Code

gitk: Implement date mode in the new framework
[git.git] / gitk
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
5 # Copyright (C) 2005-2006 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 gitdir {} {
11     global env
12     if {[info exists env(GIT_DIR)]} {
13         return $env(GIT_DIR)
14     } else {
15         return [exec git rev-parse --git-dir]
16     }
17 }
19 # A simple scheduler for compute-intensive stuff.
20 # The aim is to make sure that event handlers for GUI actions can
21 # run at least every 50-100 ms.  Unfortunately fileevent handlers are
22 # run before X event handlers, so reading from a fast source can
23 # make the GUI completely unresponsive.
24 proc run args {
25     global isonrunq runq
27     set script $args
28     if {[info exists isonrunq($script)]} return
29     if {$runq eq {}} {
30         after idle dorunq
31     }
32     lappend runq [list {} $script]
33     set isonrunq($script) 1
34 }
36 proc filerun {fd script} {
37     fileevent $fd readable [list filereadable $fd $script]
38 }
40 proc filereadable {fd script} {
41     global runq
43     fileevent $fd readable {}
44     if {$runq eq {}} {
45         after idle dorunq
46     }
47     lappend runq [list $fd $script]
48 }
50 proc nukefile {fd} {
51     global runq
53     for {set i 0} {$i < [llength $runq]} {} {
54         if {[lindex $runq $i 0] eq $fd} {
55             set runq [lreplace $runq $i $i]
56         } else {
57             incr i
58         }
59     }
60 }
62 proc dorunq {} {
63     global isonrunq runq
65     set tstart [clock clicks -milliseconds]
66     set t0 $tstart
67     while {[llength $runq] > 0} {
68         set fd [lindex $runq 0 0]
69         set script [lindex $runq 0 1]
70         set repeat [eval $script]
71         set t1 [clock clicks -milliseconds]
72         set t [expr {$t1 - $t0}]
73         set runq [lrange $runq 1 end]
74         if {$repeat ne {} && $repeat} {
75             if {$fd eq {} || $repeat == 2} {
76                 # script returns 1 if it wants to be readded
77                 # file readers return 2 if they could do more straight away
78                 lappend runq [list $fd $script]
79             } else {
80                 fileevent $fd readable [list filereadable $fd $script]
81             }
82         } elseif {$fd eq {}} {
83             unset isonrunq($script)
84         }
85         set t0 $t1
86         if {$t1 - $tstart >= 80} break
87     }
88     if {$runq ne {}} {
89         after idle dorunq
90     }
91 }
93 # Start off a git rev-list process and arrange to read its output
94 proc start_rev_list {view} {
95     global startmsecs
96     global commfd leftover tclencoding datemode
97     global viewargs viewfiles commitidx viewcomplete vnextroot
98     global showlocalchanges commitinterest mainheadid
99     global progressdirn progresscoords proglastnc curview
100     global viewincl viewactive loginstance viewinstances
102     set startmsecs [clock clicks -milliseconds]
103     set commitidx($view) 0
104     set viewcomplete($view) 0
105     set viewactive($view) 1
106     set vnextroot($view) 0
107     varcinit $view
109     set commits [eval exec git rev-parse --default HEAD --revs-only \
110                      $viewargs($view)]
111     set viewincl($view) {}
112     foreach c $commits {
113         if {![string match "^*" $c]} {
114             lappend viewincl($view) $c
115         }
116     }
117     if {[catch {
118         set fd [open [concat | git log --no-color -z --pretty=raw --parents \
119                          --boundary $commits "--" $viewfiles($view)] r]
120     } err]} {
121         error_popup "Error executing git log: $err"
122         exit 1
123     }
124     set i [incr loginstance]
125     set viewinstances($view) [list $i]
126     set commfd($i) $fd
127     set leftover($i) {}
128     if {$showlocalchanges} {
129         lappend commitinterest($mainheadid) {dodiffindex}
130     }
131     fconfigure $fd -blocking 0 -translation lf -eofchar {}
132     if {$tclencoding != {}} {
133         fconfigure $fd -encoding $tclencoding
134     }
135     filerun $fd [list getcommitlines $fd $i $view]
136     nowbusy $view "Reading"
137     if {$view == $curview} {
138         set progressdirn 1
139         set progresscoords {0 0}
140         set proglastnc 0
141     }
144 proc stop_rev_list {view} {
145     global commfd viewinstances leftover
147     foreach inst $viewinstances($view) {
148         set fd $commfd($inst)
149         catch {
150             set pid [pid $fd]
151             exec kill $pid
152         }
153         catch {close $fd}
154         nukefile $fd
155         unset commfd($inst)
156         unset leftover($inst)
157     }
158     set viewinstances($view) {}
161 proc getcommits {} {
162     global canv curview
164     initlayout
165     start_rev_list $curview
166     show_status "Reading commits..."
169 proc updatecommits {} {
170     global curview viewargs viewfiles viewincl viewinstances
171     global viewactive viewcomplete loginstance tclencoding mainheadid
172     global varcid startmsecs commfd showneartags showlocalchanges leftover
174     if {$showlocalchanges && [commitinview $mainheadid $curview]} {
175         dodiffindex
176     }
177     set view $curview
178     set commits [exec git rev-parse --default HEAD --revs-only \
179                      $viewargs($view)]
180     set pos {}
181     set neg {}
182     foreach c $commits {
183         if {[string match "^*" $c]} {
184             lappend neg $c
185         } else {
186             if {!([info exists varcid($view,$c)] ||
187                   [lsearch -exact $viewincl($view) $c] >= 0)} {
188                 lappend pos $c
189             }
190         }
191     }
192     if {$pos eq {}} {
193         return
194     }
195     foreach id $viewincl($view) {
196         lappend neg "^$id"
197     }
198     set viewincl($view) [concat $viewincl($view) $pos]
199     if {[catch {
200         set fd [open [concat | git log --no-color -z --pretty=raw --parents \
201                          --boundary $pos $neg "--" $viewfiles($view)] r]
202     } err]} {
203         error_popup "Error executing git log: $err"
204         exit 1
205     }
206     if {$viewactive($view) == 0} {
207         set startmsecs [clock clicks -milliseconds]
208     }
209     set i [incr loginstance]
210     lappend viewinstances($view) $i
211     set commfd($i) $fd
212     set leftover($i) {}
213     fconfigure $fd -blocking 0 -translation lf -eofchar {}
214     if {$tclencoding != {}} {
215         fconfigure $fd -encoding $tclencoding
216     }
217     filerun $fd [list getcommitlines $fd $i $view]
218     incr viewactive($view)
219     set viewcomplete($view) 0
220     nowbusy $view "Reading"
221     readrefs
222     changedrefs
223     if {$showneartags} {
224         getallcommits
225     }
228 proc reloadcommits {} {
229     global curview viewcomplete selectedline currentid thickerline
230     global showneartags treediffs commitinterest cached_commitrow
231     global progresscoords
233     if {!$viewcomplete($curview)} {
234         stop_rev_list $curview
235         set progresscoords {0 0}
236         adjustprogress
237     }
238     resetvarcs $curview
239     catch {unset selectedline}
240     catch {unset currentid}
241     catch {unset thickerline}
242     catch {unset treediffs}
243     readrefs
244     changedrefs
245     if {$showneartags} {
246         getallcommits
247     }
248     clear_display
249     catch {unset commitinterest}
250     catch {unset cached_commitrow}
251     setcanvscroll
252     getcommits
255 # This makes a string representation of a positive integer which
256 # sorts as a string in numerical order
257 proc strrep {n} {
258     if {$n < 16} {
259         return [format "%x" $n]
260     } elseif {$n < 256} {
261         return [format "x%.2x" $n]
262     } elseif {$n < 65536} {
263         return [format "y%.4x" $n]
264     }
265     return [format "z%.8x" $n]
268 # Procedures used in reordering commits from git log (without
269 # --topo-order) into the order for display.
271 proc varcinit {view} {
272     global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
273     global vtokmod varcmod vrowmod varcix vlastins
275     set varcstart($view) {{}}
276     set vupptr($view) {0}
277     set vdownptr($view) {0}
278     set vleftptr($view) {0}
279     set vbackptr($view) {0}
280     set varctok($view) {{}}
281     set varcrow($view) {{}}
282     set vtokmod($view) {}
283     set varcmod($view) 0
284     set vrowmod($view) 0
285     set varcix($view) {{}}
286     set vlastins($view) {0}
289 proc resetvarcs {view} {
290     global varcid varccommits parents children vseedcount ordertok
292     foreach vid [array names varcid $view,*] {
293         unset varcid($vid)
294         unset children($vid)
295         unset parents($vid)
296     }
297     # some commits might have children but haven't been seen yet
298     foreach vid [array names children $view,*] {
299         unset children($vid)
300     }
301     foreach va [array names varccommits $view,*] {
302         unset varccommits($va)
303     }
304     foreach vd [array names vseedcount $view,*] {
305         unset vseedcount($vd)
306     }
307     catch {unset ordertok}
310 proc newvarc {view id} {
311     global varcid varctok parents children datemode
312     global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
313     global commitdata commitinfo vseedcount varccommits vlastins
315     set a [llength $varctok($view)]
316     set vid $view,$id
317     if {[llength $children($vid)] == 0 || $datemode} {
318         if {![info exists commitinfo($id)]} {
319             parsecommit $id $commitdata($id) 1
320         }
321         set cdate [lindex $commitinfo($id) 4]
322         if {![string is integer -strict $cdate]} {
323             set cdate 0
324         }
325         if {![info exists vseedcount($view,$cdate)]} {
326             set vseedcount($view,$cdate) -1
327         }
328         set c [incr vseedcount($view,$cdate)]
329         set cdate [expr {$cdate ^ 0xffffffff}]
330         set tok "s[strrep $cdate][strrep $c]"
331     } else {
332         set tok {}
333     }
334     set ka 0
335     if {[llength $children($vid)] > 0} {
336         set kid [lindex $children($vid) end]
337         set k $varcid($view,$kid)
338         if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
339             set ki $kid
340             set ka $k
341             set tok [lindex $varctok($view) $k]
342         }
343     }
344     if {$ka != 0} {
345         set i [lsearch -exact $parents($view,$ki) $id]
346         set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
347         append tok [strrep $j]
348     }
349     set c [lindex $vlastins($view) $ka]
350     if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
351         set c $ka
352         set b [lindex $vdownptr($view) $ka]
353     } else {
354         set b [lindex $vleftptr($view) $c]
355     }
356     while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
357         set c $b
358         set b [lindex $vleftptr($view) $c]
359     }
360     if {$c == $ka} {
361         lset vdownptr($view) $ka $a
362         lappend vbackptr($view) 0
363     } else {
364         lset vleftptr($view) $c $a
365         lappend vbackptr($view) $c
366     }
367     lset vlastins($view) $ka $a
368     lappend vupptr($view) $ka
369     lappend vleftptr($view) $b
370     if {$b != 0} {
371         lset vbackptr($view) $b $a
372     }
373     lappend varctok($view) $tok
374     lappend varcstart($view) $id
375     lappend vdownptr($view) 0
376     lappend varcrow($view) {}
377     lappend varcix($view) {}
378     set varccommits($view,$a) {}
379     lappend vlastins($view) 0
380     return $a
383 proc splitvarc {p v} {
384     global varcid varcstart varccommits varctok
385     global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
387     set oa $varcid($v,$p)
388     set ac $varccommits($v,$oa)
389     set i [lsearch -exact $varccommits($v,$oa) $p]
390     if {$i <= 0} return
391     set na [llength $varctok($v)]
392     # "%" sorts before "0"...
393     set tok "[lindex $varctok($v) $oa]%[strrep $i]"
394     lappend varctok($v) $tok
395     lappend varcrow($v) {}
396     lappend varcix($v) {}
397     set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
398     set varccommits($v,$na) [lrange $ac $i end]
399     lappend varcstart($v) $p
400     foreach id $varccommits($v,$na) {
401         set varcid($v,$id) $na
402     }
403     lappend vdownptr($v) [lindex $vdownptr($v) $oa]
404     lset vdownptr($v) $oa $na
405     lappend vupptr($v) $oa
406     lappend vleftptr($v) 0
407     lappend vbackptr($v) 0
408     lappend vlastins($v) 0
409     for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
410         lset vupptr($v) $b $na
411     }
414 proc renumbervarc {a v} {
415     global parents children varctok varcstart varccommits
416     global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod datemode
418     set t1 [clock clicks -milliseconds]
419     set todo {}
420     set isrelated($a) 1
421     set kidchanged($a) 1
422     set ntot 0
423     while {$a != 0} {
424         if {[info exists isrelated($a)]} {
425             lappend todo $a
426             set id [lindex $varccommits($v,$a) end]
427             foreach p $parents($v,$id) {
428                 if {[info exists varcid($v,$p)]} {
429                     set isrelated($varcid($v,$p)) 1
430                 }
431             }
432         }
433         incr ntot
434         set b [lindex $vdownptr($v) $a]
435         if {$b == 0} {
436             while {$a != 0} {
437                 set b [lindex $vleftptr($v) $a]
438                 if {$b != 0} break
439                 set a [lindex $vupptr($v) $a]
440             }
441         }
442         set a $b
443     }
444     foreach a $todo {
445         if {![info exists kidchanged($a)]} continue
446         set id [lindex $varcstart($v) $a]
447         if {[llength $children($v,$id)] > 1} {
448             set children($v,$id) [lsort -command [list vtokcmp $v] \
449                                       $children($v,$id)]
450         }
451         set oldtok [lindex $varctok($v) $a]
452         if {!$datemode} {
453             set tok {}
454         } else {
455             set tok $oldtok
456         }
457         set ka 0
458         if {[llength $children($v,$id)] > 0} {
459             set kid [lindex $children($v,$id) end]
460             set k $varcid($v,$kid)
461             if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
462                 set ki $kid
463                 set ka $k
464                 set tok [lindex $varctok($v) $k]
465             }
466         }
467         if {$ka != 0} {
468             set i [lsearch -exact $parents($v,$ki) $id]
469             set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
470             append tok [strrep $j]
471         }
472         if {$tok eq $oldtok} {
473             continue
474         }
475         set id [lindex $varccommits($v,$a) end]
476         foreach p $parents($v,$id) {
477             if {[info exists varcid($v,$p)]} {
478                 set kidchanged($varcid($v,$p)) 1
479             } else {
480                 set sortkids($p) 1
481             }
482         }
483         lset varctok($v) $a $tok
484         set b [lindex $vupptr($v) $a]
485         if {$b != $ka} {
486             if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
487                 modify_arc $v $ka
488             }
489             if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
490                 modify_arc $v $b
491             }
492             set c [lindex $vbackptr($v) $a]
493             set d [lindex $vleftptr($v) $a]
494             if {$c == 0} {
495                 lset vdownptr($v) $b $d
496             } else {
497                 lset vleftptr($v) $c $d
498             }
499             if {$d != 0} {
500                 lset vbackptr($v) $d $c
501             }
502             lset vupptr($v) $a $ka
503             set c [lindex $vlastins($v) $ka]
504             if {$c == 0 || \
505                     [string compare $tok [lindex $varctok($v) $c]] < 0} {
506                 set c $ka
507                 set b [lindex $vdownptr($v) $ka]
508             } else {
509                 set b [lindex $vleftptr($v) $c]
510             }
511             while {$b != 0 && \
512                       [string compare $tok [lindex $varctok($v) $b]] >= 0} {
513                 set c $b
514                 set b [lindex $vleftptr($v) $c]
515             }
516             if {$c == $ka} {
517                 lset vdownptr($v) $ka $a
518                 lset vbackptr($v) $a 0
519             } else {
520                 lset vleftptr($v) $c $a
521                 lset vbackptr($v) $a $c
522             }
523             lset vleftptr($v) $a $b
524             if {$b != 0} {
525                 lset vbackptr($v) $b $a
526             }
527             lset vlastins($v) $ka $a
528         }
529     }
530     foreach id [array names sortkids] {
531         if {[llength $children($v,$id)] > 1} {
532             set children($v,$id) [lsort -command [list vtokcmp $v] \
533                                       $children($v,$id)]
534         }
535     }
536     set t2 [clock clicks -milliseconds]
537     #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
540 proc fix_reversal {p a v} {
541     global varcid varcstart varctok vupptr
543     set pa $varcid($v,$p)
544     if {$p ne [lindex $varcstart($v) $pa]} {
545         splitvarc $p $v
546         set pa $varcid($v,$p)
547     }
548     # seeds always need to be renumbered
549     if {[lindex $vupptr($v) $pa] == 0 ||
550         [string compare [lindex $varctok($v) $a] \
551              [lindex $varctok($v) $pa]] > 0} {
552         renumbervarc $pa $v
553     }
556 proc insertrow {id p v} {
557     global varcid varccommits parents children cmitlisted
558     global commitidx varctok vtokmod
560     set a $varcid($v,$p)
561     set i [lsearch -exact $varccommits($v,$a) $p]
562     if {$i < 0} {
563         puts "oops: insertrow can't find [shortids $p] on arc $a"
564         return
565     }
566     set children($v,$id) {}
567     set parents($v,$id) [list $p]
568     set varcid($v,$id) $a
569     lappend children($v,$p) $id
570     set cmitlisted($v,$id) 1
571     incr commitidx($v)
572     # note we deliberately don't update varcstart($v) even if $i == 0
573     set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
574     if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
575         modify_arc $v $a $i
576     }
577     drawvisible
580 proc removerow {id v} {
581     global varcid varccommits parents children commitidx
582     global varctok vtokmod cmitlisted
584     if {[llength $parents($v,$id)] != 1} {
585         puts "oops: removerow [shortids $id] has [llength $parents($v,$id)] parents"
586         return
587     }
588     set p [lindex $parents($v,$id) 0]
589     set a $varcid($v,$id)
590     set i [lsearch -exact $varccommits($v,$a) $id]
591     if {$i < 0} {
592         puts "oops: removerow can't find [shortids $id] on arc $a"
593         return
594     }
595     unset varcid($v,$id)
596     set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
597     unset parents($v,$id)
598     unset children($v,$id)
599     unset cmitlisted($v,$id)
600     incr commitidx($v) -1
601     set j [lsearch -exact $children($v,$p) $id]
602     if {$j >= 0} {
603         set children($v,$p) [lreplace $children($v,$p) $j $j]
604     }
605     if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
606         modify_arc $v $a $i
607     }
608     drawvisible
611 proc vtokcmp {v a b} {
612     global varctok varcid
614     return [string compare [lindex $varctok($v) $varcid($v,$a)] \
615                 [lindex $varctok($v) $varcid($v,$b)]]
618 proc modify_arc {v a {lim {}}} {
619     global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
620     global vhighlights nhighlights fhighlights rhighlights
622     set vtokmod($v) [lindex $varctok($v) $a]
623     set varcmod($v) $a
624     if {$v == $curview} {
625         while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
626             set a [lindex $vupptr($v) $a]
627             set lim {}
628         }
629         set r 0
630         if {$a != 0} {
631             if {$lim eq {}} {
632                 set lim [llength $varccommits($v,$a)]
633             }
634             set r [expr {[lindex $varcrow($v) $a] + $lim}]
635         }
636         set vrowmod($v) $r
637         undolayout $r
638     }
639     catch {unset nhighlights}
640     catch {unset fhighlights}
641     catch {unset vhighlights}
642     catch {unset rhighlights}
645 proc update_arcrows {v} {
646     global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
647     global varcid vrownum varcorder varcix varccommits
648     global vupptr vdownptr vleftptr varctok
649     global displayorder parentlist curview cached_commitrow
651     set narctot [expr {[llength $varctok($v)] - 1}]
652     set a $varcmod($v)
653     while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
654         # go up the tree until we find something that has a row number,
655         # or we get to a seed
656         set a [lindex $vupptr($v) $a]
657     }
658     if {$a == 0} {
659         set a [lindex $vdownptr($v) 0]
660         if {$a == 0} return
661         set vrownum($v) {0}
662         set varcorder($v) [list $a]
663         lset varcix($v) $a 0
664         lset varcrow($v) $a 0
665         set arcn 0
666         set row 0
667     } else {
668         set arcn [lindex $varcix($v) $a]
669         # see if a is the last arc; if so, nothing to do
670         if {$arcn == $narctot - 1} {
671             return
672         }
673         if {[llength $vrownum($v)] > $arcn + 1} {
674             set vrownum($v) [lrange $vrownum($v) 0 $arcn]
675             set varcorder($v) [lrange $varcorder($v) 0 $arcn]
676         }
677         set row [lindex $varcrow($v) $a]
678     }
679     if {$v == $curview} {
680         if {[llength $displayorder] > $vrowmod($v)} {
681             set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
682             set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
683         }
684         catch {unset cached_commitrow}
685     }
686     while {1} {
687         set p $a
688         incr row [llength $varccommits($v,$a)]
689         # go down if possible
690         set b [lindex $vdownptr($v) $a]
691         if {$b == 0} {
692             # if not, go left, or go up until we can go left
693             while {$a != 0} {
694                 set b [lindex $vleftptr($v) $a]
695                 if {$b != 0} break
696                 set a [lindex $vupptr($v) $a]
697             }
698             if {$a == 0} break
699         }
700         set a $b
701         incr arcn
702         lappend vrownum($v) $row
703         lappend varcorder($v) $a
704         lset varcix($v) $a $arcn
705         lset varcrow($v) $a $row
706     }
707     set vtokmod($v) [lindex $varctok($v) $p]
708     set varcmod($v) $p
709     set vrowmod($v) $row
710     if {[info exists currentid]} {
711         set selectedline [rowofcommit $currentid]
712     }
715 # Test whether view $v contains commit $id
716 proc commitinview {id v} {
717     global varcid
719     return [info exists varcid($v,$id)]
722 # Return the row number for commit $id in the current view
723 proc rowofcommit {id} {
724     global varcid varccommits varcrow curview cached_commitrow
725     global varctok vtokmod
727     if {[info exists cached_commitrow($id)]} {
728         return $cached_commitrow($id)
729     }
730     set v $curview
731     if {![info exists varcid($v,$id)]} {
732         puts "oops rowofcommit no arc for [shortids $id]"
733         return {}
734     }
735     set a $varcid($v,$id)
736     if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] > 0} {
737         update_arcrows $v
738     }
739     set i [lsearch -exact $varccommits($v,$a) $id]
740     if {$i < 0} {
741         puts "oops didn't find commit [shortids $id] in arc $a"
742         return {}
743     }
744     incr i [lindex $varcrow($v) $a]
745     set cached_commitrow($id) $i
746     return $i
749 proc bsearch {l elt} {
750     if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
751         return 0
752     }
753     set lo 0
754     set hi [llength $l]
755     while {$hi - $lo > 1} {
756         set mid [expr {int(($lo + $hi) / 2)}]
757         set t [lindex $l $mid]
758         if {$elt < $t} {
759             set hi $mid
760         } elseif {$elt > $t} {
761             set lo $mid
762         } else {
763             return $mid
764         }
765     }
766     return $lo
769 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
770 proc make_disporder {start end} {
771     global vrownum curview commitidx displayorder parentlist
772     global varccommits varcorder parents vrowmod varcrow
773     global d_valid_start d_valid_end
775     if {$end > $vrowmod($curview)} {
776         update_arcrows $curview
777     }
778     set ai [bsearch $vrownum($curview) $start]
779     set start [lindex $vrownum($curview) $ai]
780     set narc [llength $vrownum($curview)]
781     for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
782         set a [lindex $varcorder($curview) $ai]
783         set l [llength $displayorder]
784         set al [llength $varccommits($curview,$a)]
785         if {$l < $r + $al} {
786             if {$l < $r} {
787                 set pad [ntimes [expr {$r - $l}] {}]
788                 set displayorder [concat $displayorder $pad]
789                 set parentlist [concat $parentlist $pad]
790             } elseif {$l > $r} {
791                 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
792                 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
793             }
794             foreach id $varccommits($curview,$a) {
795                 lappend displayorder $id
796                 lappend parentlist $parents($curview,$id)
797             }
798         } elseif {[lindex $displayorder $r] eq {}} {
799             set i $r
800             foreach id $varccommits($curview,$a) {
801                 lset displayorder $i $id
802                 lset parentlist $i $parents($curview,$id)
803                 incr i
804             }
805         }
806         incr r $al
807     }
810 proc commitonrow {row} {
811     global displayorder
813     set id [lindex $displayorder $row]
814     if {$id eq {}} {
815         make_disporder $row [expr {$row + 1}]
816         set id [lindex $displayorder $row]
817     }
818     return $id
821 proc closevarcs {v} {
822     global varctok varccommits varcid parents children
823     global cmitlisted commitidx commitinterest vtokmod
825     set missing_parents 0
826     set scripts {}
827     set narcs [llength $varctok($v)]
828     for {set a 1} {$a < $narcs} {incr a} {
829         set id [lindex $varccommits($v,$a) end]
830         foreach p $parents($v,$id) {
831             if {[info exists varcid($v,$p)]} continue
832             # add p as a new commit
833             incr missing_parents
834             set cmitlisted($v,$p) 0
835             set parents($v,$p) {}
836             if {[llength $children($v,$p)] == 1 &&
837                 [llength $parents($v,$id)] == 1} {
838                 set b $a
839             } else {
840                 set b [newvarc $v $p]
841             }
842             set varcid($v,$p) $b
843             if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
844                 modify_arc $v $b
845             }
846             lappend varccommits($v,$b) $p
847             incr commitidx($v)
848             if {[info exists commitinterest($p)]} {
849                 foreach script $commitinterest($p) {
850                     lappend scripts [string map [list "%I" $p] $script]
851                 }
852                 unset commitinterest($id)
853             }
854         }
855     }
856     if {$missing_parents > 0} {
857         foreach s $scripts {
858             eval $s
859         }
860     }
863 proc getcommitlines {fd inst view}  {
864     global cmitlisted commitinterest leftover
865     global commitidx commitdata datemode
866     global parents children curview hlview
867     global vnextroot idpending ordertok
868     global varccommits varcid varctok vtokmod
870     set stuff [read $fd 500000]
871     # git log doesn't terminate the last commit with a null...
872     if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
873         set stuff "\0"
874     }
875     if {$stuff == {}} {
876         if {![eof $fd]} {
877             return 1
878         }
879         global commfd viewcomplete viewactive viewname progresscoords
880         global viewinstances
881         unset commfd($inst)
882         set i [lsearch -exact $viewinstances($view) $inst]
883         if {$i >= 0} {
884             set viewinstances($view) [lreplace $viewinstances($view) $i $i]
885         }
886         # set it blocking so we wait for the process to terminate
887         fconfigure $fd -blocking 1
888         if {[catch {close $fd} err]} {
889             set fv {}
890             if {$view != $curview} {
891                 set fv " for the \"$viewname($view)\" view"
892             }
893             if {[string range $err 0 4] == "usage"} {
894                 set err "Gitk: error reading commits$fv:\
895                         bad arguments to git rev-list."
896                 if {$viewname($view) eq "Command line"} {
897                     append err \
898                         "  (Note: arguments to gitk are passed to git rev-list\
899                          to allow selection of commits to be displayed.)"
900                 }
901             } else {
902                 set err "Error reading commits$fv: $err"
903             }
904             error_popup $err
905         }
906         if {[incr viewactive($view) -1] <= 0} {
907             set viewcomplete($view) 1
908             # Check if we have seen any ids listed as parents that haven't
909             # appeared in the list
910             closevarcs $view
911             notbusy $view
912             set progresscoords {0 0}
913             adjustprogress
914         }
915         if {$view == $curview} {
916             run chewcommits $view
917         }
918         return 0
919     }
920     set start 0
921     set gotsome 0
922     set scripts {}
923     while 1 {
924         set i [string first "\0" $stuff $start]
925         if {$i < 0} {
926             append leftover($inst) [string range $stuff $start end]
927             break
928         }
929         if {$start == 0} {
930             set cmit $leftover($inst)
931             append cmit [string range $stuff 0 [expr {$i - 1}]]
932             set leftover($inst) {}
933         } else {
934             set cmit [string range $stuff $start [expr {$i - 1}]]
935         }
936         set start [expr {$i + 1}]
937         set j [string first "\n" $cmit]
938         set ok 0
939         set listed 1
940         if {$j >= 0 && [string match "commit *" $cmit]} {
941             set ids [string range $cmit 7 [expr {$j - 1}]]
942             if {[string match {[-<>]*} $ids]} {
943                 switch -- [string index $ids 0] {
944                     "-" {set listed 0}
945                     "<" {set listed 2}
946                     ">" {set listed 3}
947                 }
948                 set ids [string range $ids 1 end]
949             }
950             set ok 1
951             foreach id $ids {
952                 if {[string length $id] != 40} {
953                     set ok 0
954                     break
955                 }
956             }
957         }
958         if {!$ok} {
959             set shortcmit $cmit
960             if {[string length $shortcmit] > 80} {
961                 set shortcmit "[string range $shortcmit 0 80]..."
962             }
963             error_popup "Can't parse git log output: {$shortcmit}"
964             exit 1
965         }
966         set id [lindex $ids 0]
967         set vid $view,$id
968         if {!$listed && [info exists parents($vid)]} continue
969         if {$listed} {
970             set olds [lrange $ids 1 end]
971         } else {
972             set olds {}
973         }
974         set commitdata($id) [string range $cmit [expr {$j + 1}] end]
975         set cmitlisted($vid) $listed
976         set parents($vid) $olds
977         set a 0
978         if {![info exists children($vid)]} {
979             set children($vid) {}
980         } elseif {[llength $children($vid)] == 1} {
981             set k [lindex $children($vid) 0]
982             if {[llength $parents($view,$k)] == 1 &&
983                 (!$datemode ||
984                  $varcid($view,$k) == [llength $varctok($view)] - 1)} {
985                 set a $varcid($view,$k)
986             }
987         }
988         if {$a == 0} {
989             # new arc
990             set a [newvarc $view $id]
991         }
992         set varcid($vid) $a
993         if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
994             modify_arc $view $a
995         }
996         lappend varccommits($view,$a) $id
998         set i 0
999         foreach p $olds {
1000             if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1001                 set vp $view,$p
1002                 if {[llength [lappend children($vp) $id]] > 1 &&
1003                     [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1004                     set children($vp) [lsort -command [list vtokcmp $view] \
1005                                            $children($vp)]
1006                     catch {unset ordertok}
1007                 }
1008                 if {[info exists varcid($view,$p)]} {
1009                     fix_reversal $p $a $view
1010                 }
1011             }
1012             incr i
1013         }
1015         incr commitidx($view)
1016         if {[info exists commitinterest($id)]} {
1017             foreach script $commitinterest($id) {
1018                 lappend scripts [string map [list "%I" $id] $script]
1019             }
1020             unset commitinterest($id)
1021         }
1022         set gotsome 1
1023     }
1024     if {$gotsome} {
1025         run chewcommits $view
1026         foreach s $scripts {
1027             eval $s
1028         }
1029         if {$view == $curview} {
1030             # update progress bar
1031             global progressdirn progresscoords proglastnc
1032             set inc [expr {($commitidx($view) - $proglastnc) * 0.0002}]
1033             set proglastnc $commitidx($view)
1034             set l [lindex $progresscoords 0]
1035             set r [lindex $progresscoords 1]
1036             if {$progressdirn} {
1037                 set r [expr {$r + $inc}]
1038                 if {$r >= 1.0} {
1039                     set r 1.0
1040                     set progressdirn 0
1041                 }
1042                 if {$r > 0.2} {
1043                     set l [expr {$r - 0.2}]
1044                 }
1045             } else {
1046                 set l [expr {$l - $inc}]
1047                 if {$l <= 0.0} {
1048                     set l 0.0
1049                     set progressdirn 1
1050                 }
1051                 set r [expr {$l + 0.2}]
1052             }
1053             set progresscoords [list $l $r]
1054             adjustprogress
1055         }
1056     }
1057     return 2
1060 proc chewcommits {view} {
1061     global curview hlview viewcomplete
1062     global pending_select
1064     if {$view == $curview} {
1065         layoutmore
1066         if {$viewcomplete($view)} {
1067             global commitidx varctok
1068             global numcommits startmsecs
1069             global mainheadid commitinfo nullid
1071             if {[info exists pending_select]} {
1072                 set row [first_real_row]
1073                 selectline $row 1
1074             }
1075             if {$commitidx($curview) > 0} {
1076                 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1077                 #puts "overall $ms ms for $numcommits commits"
1078                 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1079             } else {
1080                 show_status "No commits selected"
1081             }
1082             notbusy layout
1083         }
1084     }
1085     if {[info exists hlview] && $view == $hlview} {
1086         vhighlightmore
1087     }
1088     return 0
1091 proc readcommit {id} {
1092     if {[catch {set contents [exec git cat-file commit $id]}]} return
1093     parsecommit $id $contents 0
1096 proc parsecommit {id contents listed} {
1097     global commitinfo cdate
1099     set inhdr 1
1100     set comment {}
1101     set headline {}
1102     set auname {}
1103     set audate {}
1104     set comname {}
1105     set comdate {}
1106     set hdrend [string first "\n\n" $contents]
1107     if {$hdrend < 0} {
1108         # should never happen...
1109         set hdrend [string length $contents]
1110     }
1111     set header [string range $contents 0 [expr {$hdrend - 1}]]
1112     set comment [string range $contents [expr {$hdrend + 2}] end]
1113     foreach line [split $header "\n"] {
1114         set tag [lindex $line 0]
1115         if {$tag == "author"} {
1116             set audate [lindex $line end-1]
1117             set auname [lrange $line 1 end-2]
1118         } elseif {$tag == "committer"} {
1119             set comdate [lindex $line end-1]
1120             set comname [lrange $line 1 end-2]
1121         }
1122     }
1123     set headline {}
1124     # take the first non-blank line of the comment as the headline
1125     set headline [string trimleft $comment]
1126     set i [string first "\n" $headline]
1127     if {$i >= 0} {
1128         set headline [string range $headline 0 $i]
1129     }
1130     set headline [string trimright $headline]
1131     set i [string first "\r" $headline]
1132     if {$i >= 0} {
1133         set headline [string trimright [string range $headline 0 $i]]
1134     }
1135     if {!$listed} {
1136         # git rev-list indents the comment by 4 spaces;
1137         # if we got this via git cat-file, add the indentation
1138         set newcomment {}
1139         foreach line [split $comment "\n"] {
1140             append newcomment "    "
1141             append newcomment $line
1142             append newcomment "\n"
1143         }
1144         set comment $newcomment
1145     }
1146     if {$comdate != {}} {
1147         set cdate($id) $comdate
1148     }
1149     set commitinfo($id) [list $headline $auname $audate \
1150                              $comname $comdate $comment]
1153 proc getcommit {id} {
1154     global commitdata commitinfo
1156     if {[info exists commitdata($id)]} {
1157         parsecommit $id $commitdata($id) 1
1158     } else {
1159         readcommit $id
1160         if {![info exists commitinfo($id)]} {
1161             set commitinfo($id) {"No commit information available"}
1162         }
1163     }
1164     return 1
1167 proc readrefs {} {
1168     global tagids idtags headids idheads tagobjid
1169     global otherrefids idotherrefs mainhead mainheadid
1171     foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1172         catch {unset $v}
1173     }
1174     set refd [open [list | git show-ref -d] r]
1175     while {[gets $refd line] >= 0} {
1176         if {[string index $line 40] ne " "} continue
1177         set id [string range $line 0 39]
1178         set ref [string range $line 41 end]
1179         if {![string match "refs/*" $ref]} continue
1180         set name [string range $ref 5 end]
1181         if {[string match "remotes/*" $name]} {
1182             if {![string match "*/HEAD" $name]} {
1183                 set headids($name) $id
1184                 lappend idheads($id) $name
1185             }
1186         } elseif {[string match "heads/*" $name]} {
1187             set name [string range $name 6 end]
1188             set headids($name) $id
1189             lappend idheads($id) $name
1190         } elseif {[string match "tags/*" $name]} {
1191             # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1192             # which is what we want since the former is the commit ID
1193             set name [string range $name 5 end]
1194             if {[string match "*^{}" $name]} {
1195                 set name [string range $name 0 end-3]
1196             } else {
1197                 set tagobjid($name) $id
1198             }
1199             set tagids($name) $id
1200             lappend idtags($id) $name
1201         } else {
1202             set otherrefids($name) $id
1203             lappend idotherrefs($id) $name
1204         }
1205     }
1206     catch {close $refd}
1207     set mainhead {}
1208     set mainheadid {}
1209     catch {
1210         set thehead [exec git symbolic-ref HEAD]
1211         if {[string match "refs/heads/*" $thehead]} {
1212             set mainhead [string range $thehead 11 end]
1213             if {[info exists headids($mainhead)]} {
1214                 set mainheadid $headids($mainhead)
1215             }
1216         }
1217     }
1220 # skip over fake commits
1221 proc first_real_row {} {
1222     global nullid nullid2 numcommits
1224     for {set row 0} {$row < $numcommits} {incr row} {
1225         set id [commitonrow $row]
1226         if {$id ne $nullid && $id ne $nullid2} {
1227             break
1228         }
1229     }
1230     return $row
1233 # update things for a head moved to a child of its previous location
1234 proc movehead {id name} {
1235     global headids idheads
1237     removehead $headids($name) $name
1238     set headids($name) $id
1239     lappend idheads($id) $name
1242 # update things when a head has been removed
1243 proc removehead {id name} {
1244     global headids idheads
1246     if {$idheads($id) eq $name} {
1247         unset idheads($id)
1248     } else {
1249         set i [lsearch -exact $idheads($id) $name]
1250         if {$i >= 0} {
1251             set idheads($id) [lreplace $idheads($id) $i $i]
1252         }
1253     }
1254     unset headids($name)
1257 proc show_error {w top msg} {
1258     message $w.m -text $msg -justify center -aspect 400
1259     pack $w.m -side top -fill x -padx 20 -pady 20
1260     button $w.ok -text OK -command "destroy $top"
1261     pack $w.ok -side bottom -fill x
1262     bind $top <Visibility> "grab $top; focus $top"
1263     bind $top <Key-Return> "destroy $top"
1264     tkwait window $top
1267 proc error_popup msg {
1268     set w .error
1269     toplevel $w
1270     wm transient $w .
1271     show_error $w $w $msg
1274 proc confirm_popup msg {
1275     global confirm_ok
1276     set confirm_ok 0
1277     set w .confirm
1278     toplevel $w
1279     wm transient $w .
1280     message $w.m -text $msg -justify center -aspect 400
1281     pack $w.m -side top -fill x -padx 20 -pady 20
1282     button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
1283     pack $w.ok -side left -fill x
1284     button $w.cancel -text Cancel -command "destroy $w"
1285     pack $w.cancel -side right -fill x
1286     bind $w <Visibility> "grab $w; focus $w"
1287     tkwait window $w
1288     return $confirm_ok
1291 proc makewindow {} {
1292     global canv canv2 canv3 linespc charspc ctext cflist
1293     global tabstop
1294     global findtype findtypemenu findloc findstring fstring geometry
1295     global entries sha1entry sha1string sha1but
1296     global diffcontextstring diffcontext
1297     global maincursor textcursor curtextcursor
1298     global rowctxmenu fakerowmenu mergemax wrapcomment
1299     global highlight_files gdttype
1300     global searchstring sstring
1301     global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1302     global headctxmenu progresscanv progressitem progresscoords statusw
1303     global fprogitem fprogcoord lastprogupdate progupdatepending
1304     global rprogitem rprogcoord
1305     global have_tk85
1307     menu .bar
1308     .bar add cascade -label "File" -menu .bar.file
1309     .bar configure -font uifont
1310     menu .bar.file
1311     .bar.file add command -label "Update" -command updatecommits
1312     .bar.file add command -label "Reload" -command reloadcommits
1313     .bar.file add command -label "Reread references" -command rereadrefs
1314     .bar.file add command -label "List references" -command showrefs
1315     .bar.file add command -label "Quit" -command doquit
1316     .bar.file configure -font uifont
1317     menu .bar.edit
1318     .bar add cascade -label "Edit" -menu .bar.edit
1319     .bar.edit add command -label "Preferences" -command doprefs
1320     .bar.edit configure -font uifont
1322     menu .bar.view -font uifont
1323     .bar add cascade -label "View" -menu .bar.view
1324     .bar.view add command -label "New view..." -command {newview 0}
1325     .bar.view add command -label "Edit view..." -command editview \
1326         -state disabled
1327     .bar.view add command -label "Delete view" -command delview -state disabled
1328     .bar.view add separator
1329     .bar.view add radiobutton -label "All files" -command {showview 0} \
1330         -variable selectedview -value 0
1332     menu .bar.help
1333     .bar add cascade -label "Help" -menu .bar.help
1334     .bar.help add command -label "About gitk" -command about
1335     .bar.help add command -label "Key bindings" -command keys
1336     .bar.help configure -font uifont
1337     . configure -menu .bar
1339     # the gui has upper and lower half, parts of a paned window.
1340     panedwindow .ctop -orient vertical
1342     # possibly use assumed geometry
1343     if {![info exists geometry(pwsash0)]} {
1344         set geometry(topheight) [expr {15 * $linespc}]
1345         set geometry(topwidth) [expr {80 * $charspc}]
1346         set geometry(botheight) [expr {15 * $linespc}]
1347         set geometry(botwidth) [expr {50 * $charspc}]
1348         set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1349         set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1350     }
1352     # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1353     frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1354     frame .tf.histframe
1355     panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1357     # create three canvases
1358     set cscroll .tf.histframe.csb
1359     set canv .tf.histframe.pwclist.canv
1360     canvas $canv \
1361         -selectbackground $selectbgcolor \
1362         -background $bgcolor -bd 0 \
1363         -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1364     .tf.histframe.pwclist add $canv
1365     set canv2 .tf.histframe.pwclist.canv2
1366     canvas $canv2 \
1367         -selectbackground $selectbgcolor \
1368         -background $bgcolor -bd 0 -yscrollincr $linespc
1369     .tf.histframe.pwclist add $canv2
1370     set canv3 .tf.histframe.pwclist.canv3
1371     canvas $canv3 \
1372         -selectbackground $selectbgcolor \
1373         -background $bgcolor -bd 0 -yscrollincr $linespc
1374     .tf.histframe.pwclist add $canv3
1375     eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1376     eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1378     # a scroll bar to rule them
1379     scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1380     pack $cscroll -side right -fill y
1381     bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1382     lappend bglist $canv $canv2 $canv3
1383     pack .tf.histframe.pwclist -fill both -expand 1 -side left
1385     # we have two button bars at bottom of top frame. Bar 1
1386     frame .tf.bar
1387     frame .tf.lbar -height 15
1389     set sha1entry .tf.bar.sha1
1390     set entries $sha1entry
1391     set sha1but .tf.bar.sha1label
1392     button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
1393         -command gotocommit -width 8 -font uifont
1394     $sha1but conf -disabledforeground [$sha1but cget -foreground]
1395     pack .tf.bar.sha1label -side left
1396     entry $sha1entry -width 40 -font textfont -textvariable sha1string
1397     trace add variable sha1string write sha1change
1398     pack $sha1entry -side left -pady 2
1400     image create bitmap bm-left -data {
1401         #define left_width 16
1402         #define left_height 16
1403         static unsigned char left_bits[] = {
1404         0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1405         0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1406         0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1407     }
1408     image create bitmap bm-right -data {
1409         #define right_width 16
1410         #define right_height 16
1411         static unsigned char right_bits[] = {
1412         0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1413         0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1414         0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1415     }
1416     button .tf.bar.leftbut -image bm-left -command goback \
1417         -state disabled -width 26
1418     pack .tf.bar.leftbut -side left -fill y
1419     button .tf.bar.rightbut -image bm-right -command goforw \
1420         -state disabled -width 26
1421     pack .tf.bar.rightbut -side left -fill y
1423     # Status label and progress bar
1424     set statusw .tf.bar.status
1425     label $statusw -width 15 -relief sunken -font uifont
1426     pack $statusw -side left -padx 5
1427     set h [expr {[font metrics uifont -linespace] + 2}]
1428     set progresscanv .tf.bar.progress
1429     canvas $progresscanv -relief sunken -height $h -borderwidth 2
1430     set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1431     set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1432     set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1433     pack $progresscanv -side right -expand 1 -fill x
1434     set progresscoords {0 0}
1435     set fprogcoord 0
1436     set rprogcoord 0
1437     bind $progresscanv <Configure> adjustprogress
1438     set lastprogupdate [clock clicks -milliseconds]
1439     set progupdatepending 0
1441     # build up the bottom bar of upper window
1442     label .tf.lbar.flabel -text "Find " -font uifont
1443     button .tf.lbar.fnext -text "next" -command {dofind 1 1} -font uifont
1444     button .tf.lbar.fprev -text "prev" -command {dofind -1 1} -font uifont
1445     label .tf.lbar.flab2 -text " commit " -font uifont
1446     pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
1447         -side left -fill y
1448     set gdttype "containing:"
1449     set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
1450                 "containing:" \
1451                 "touching paths:" \
1452                 "adding/removing string:"]
1453     trace add variable gdttype write gdttype_change
1454     $gm conf -font uifont
1455     .tf.lbar.gdttype conf -font uifont
1456     pack .tf.lbar.gdttype -side left -fill y
1458     set findstring {}
1459     set fstring .tf.lbar.findstring
1460     lappend entries $fstring
1461     entry $fstring -width 30 -font textfont -textvariable findstring
1462     trace add variable findstring write find_change
1463     set findtype Exact
1464     set findtypemenu [tk_optionMenu .tf.lbar.findtype \
1465                       findtype Exact IgnCase Regexp]
1466     trace add variable findtype write findcom_change
1467     .tf.lbar.findtype configure -font uifont
1468     .tf.lbar.findtype.menu configure -font uifont
1469     set findloc "All fields"
1470     tk_optionMenu .tf.lbar.findloc findloc "All fields" Headline \
1471         Comments Author Committer
1472     trace add variable findloc write find_change
1473     .tf.lbar.findloc configure -font uifont
1474     .tf.lbar.findloc.menu configure -font uifont
1475     pack .tf.lbar.findloc -side right
1476     pack .tf.lbar.findtype -side right
1477     pack $fstring -side left -expand 1 -fill x
1479     # Finish putting the upper half of the viewer together
1480     pack .tf.lbar -in .tf -side bottom -fill x
1481     pack .tf.bar -in .tf -side bottom -fill x
1482     pack .tf.histframe -fill both -side top -expand 1
1483     .ctop add .tf
1484     .ctop paneconfigure .tf -height $geometry(topheight)
1485     .ctop paneconfigure .tf -width $geometry(topwidth)
1487     # now build up the bottom
1488     panedwindow .pwbottom -orient horizontal
1490     # lower left, a text box over search bar, scroll bar to the right
1491     # if we know window height, then that will set the lower text height, otherwise
1492     # we set lower text height which will drive window height
1493     if {[info exists geometry(main)]} {
1494         frame .bleft -width $geometry(botwidth)
1495     } else {
1496         frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
1497     }
1498     frame .bleft.top
1499     frame .bleft.mid
1501     button .bleft.top.search -text "Search" -command dosearch \
1502         -font uifont
1503     pack .bleft.top.search -side left -padx 5
1504     set sstring .bleft.top.sstring
1505     entry $sstring -width 20 -font textfont -textvariable searchstring
1506     lappend entries $sstring
1507     trace add variable searchstring write incrsearch
1508     pack $sstring -side left -expand 1 -fill x
1509     radiobutton .bleft.mid.diff -text "Diff" -font uifont \
1510         -command changediffdisp -variable diffelide -value {0 0}
1511     radiobutton .bleft.mid.old -text "Old version" -font uifont \
1512         -command changediffdisp -variable diffelide -value {0 1}
1513     radiobutton .bleft.mid.new -text "New version" -font uifont \
1514         -command changediffdisp -variable diffelide -value {1 0}
1515     label .bleft.mid.labeldiffcontext -text "      Lines of context: " \
1516         -font uifont
1517     pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
1518     spinbox .bleft.mid.diffcontext -width 5 -font textfont \
1519         -from 1 -increment 1 -to 10000000 \
1520         -validate all -validatecommand "diffcontextvalidate %P" \
1521         -textvariable diffcontextstring
1522     .bleft.mid.diffcontext set $diffcontext
1523     trace add variable diffcontextstring write diffcontextchange
1524     lappend entries .bleft.mid.diffcontext
1525     pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
1526     set ctext .bleft.ctext
1527     text $ctext -background $bgcolor -foreground $fgcolor \
1528         -state disabled -font textfont \
1529         -yscrollcommand scrolltext -wrap none
1530     if {$have_tk85} {
1531         $ctext conf -tabstyle wordprocessor
1532     }
1533     scrollbar .bleft.sb -command "$ctext yview"
1534     pack .bleft.top -side top -fill x
1535     pack .bleft.mid -side top -fill x
1536     pack .bleft.sb -side right -fill y
1537     pack $ctext -side left -fill both -expand 1
1538     lappend bglist $ctext
1539     lappend fglist $ctext
1541     $ctext tag conf comment -wrap $wrapcomment
1542     $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
1543     $ctext tag conf hunksep -fore [lindex $diffcolors 2]
1544     $ctext tag conf d0 -fore [lindex $diffcolors 0]
1545     $ctext tag conf d1 -fore [lindex $diffcolors 1]
1546     $ctext tag conf m0 -fore red
1547     $ctext tag conf m1 -fore blue
1548     $ctext tag conf m2 -fore green
1549     $ctext tag conf m3 -fore purple
1550     $ctext tag conf m4 -fore brown
1551     $ctext tag conf m5 -fore "#009090"
1552     $ctext tag conf m6 -fore magenta
1553     $ctext tag conf m7 -fore "#808000"
1554     $ctext tag conf m8 -fore "#009000"
1555     $ctext tag conf m9 -fore "#ff0080"
1556     $ctext tag conf m10 -fore cyan
1557     $ctext tag conf m11 -fore "#b07070"
1558     $ctext tag conf m12 -fore "#70b0f0"
1559     $ctext tag conf m13 -fore "#70f0b0"
1560     $ctext tag conf m14 -fore "#f0b070"
1561     $ctext tag conf m15 -fore "#ff70b0"
1562     $ctext tag conf mmax -fore darkgrey
1563     set mergemax 16
1564     $ctext tag conf mresult -font textfontbold
1565     $ctext tag conf msep -font textfontbold
1566     $ctext tag conf found -back yellow
1568     .pwbottom add .bleft
1569     .pwbottom paneconfigure .bleft -width $geometry(botwidth)
1571     # lower right
1572     frame .bright
1573     frame .bright.mode
1574     radiobutton .bright.mode.patch -text "Patch" \
1575         -command reselectline -variable cmitmode -value "patch"
1576     .bright.mode.patch configure -font uifont
1577     radiobutton .bright.mode.tree -text "Tree" \
1578         -command reselectline -variable cmitmode -value "tree"
1579     .bright.mode.tree configure -font uifont
1580     grid .bright.mode.patch .bright.mode.tree -sticky ew
1581     pack .bright.mode -side top -fill x
1582     set cflist .bright.cfiles
1583     set indent [font measure mainfont "nn"]
1584     text $cflist \
1585         -selectbackground $selectbgcolor \
1586         -background $bgcolor -foreground $fgcolor \
1587         -font mainfont \
1588         -tabs [list $indent [expr {2 * $indent}]] \
1589         -yscrollcommand ".bright.sb set" \
1590         -cursor [. cget -cursor] \
1591         -spacing1 1 -spacing3 1
1592     lappend bglist $cflist
1593     lappend fglist $cflist
1594     scrollbar .bright.sb -command "$cflist yview"
1595     pack .bright.sb -side right -fill y
1596     pack $cflist -side left -fill both -expand 1
1597     $cflist tag configure highlight \
1598         -background [$cflist cget -selectbackground]
1599     $cflist tag configure bold -font mainfontbold
1601     .pwbottom add .bright
1602     .ctop add .pwbottom
1604     # restore window position if known
1605     if {[info exists geometry(main)]} {
1606         wm geometry . "$geometry(main)"
1607     }
1609     if {[tk windowingsystem] eq {aqua}} {
1610         set M1B M1
1611     } else {
1612         set M1B Control
1613     }
1615     bind .pwbottom <Configure> {resizecdetpanes %W %w}
1616     pack .ctop -fill both -expand 1
1617     bindall <1> {selcanvline %W %x %y}
1618     #bindall <B1-Motion> {selcanvline %W %x %y}
1619     if {[tk windowingsystem] == "win32"} {
1620         bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
1621         bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
1622     } else {
1623         bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
1624         bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
1625         if {[tk windowingsystem] eq "aqua"} {
1626             bindall <MouseWheel> {
1627                 set delta [expr {- (%D)}]
1628                 allcanvs yview scroll $delta units
1629             }
1630         }
1631     }
1632     bindall <2> "canvscan mark %W %x %y"
1633     bindall <B2-Motion> "canvscan dragto %W %x %y"
1634     bindkey <Home> selfirstline
1635     bindkey <End> sellastline
1636     bind . <Key-Up> "selnextline -1"
1637     bind . <Key-Down> "selnextline 1"
1638     bind . <Shift-Key-Up> "dofind -1 0"
1639     bind . <Shift-Key-Down> "dofind 1 0"
1640     bindkey <Key-Right> "goforw"
1641     bindkey <Key-Left> "goback"
1642     bind . <Key-Prior> "selnextpage -1"
1643     bind . <Key-Next> "selnextpage 1"
1644     bind . <$M1B-Home> "allcanvs yview moveto 0.0"
1645     bind . <$M1B-End> "allcanvs yview moveto 1.0"
1646     bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
1647     bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
1648     bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
1649     bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
1650     bindkey <Key-Delete> "$ctext yview scroll -1 pages"
1651     bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
1652     bindkey <Key-space> "$ctext yview scroll 1 pages"
1653     bindkey p "selnextline -1"
1654     bindkey n "selnextline 1"
1655     bindkey z "goback"
1656     bindkey x "goforw"
1657     bindkey i "selnextline -1"
1658     bindkey k "selnextline 1"
1659     bindkey j "goback"
1660     bindkey l "goforw"
1661     bindkey b "$ctext yview scroll -1 pages"
1662     bindkey d "$ctext yview scroll 18 units"
1663     bindkey u "$ctext yview scroll -18 units"
1664     bindkey / {dofind 1 1}
1665     bindkey <Key-Return> {dofind 1 1}
1666     bindkey ? {dofind -1 1}
1667     bindkey f nextfile
1668     bindkey <F5> updatecommits
1669     bind . <$M1B-q> doquit
1670     bind . <$M1B-f> {dofind 1 1}
1671     bind . <$M1B-g> {dofind 1 0}
1672     bind . <$M1B-r> dosearchback
1673     bind . <$M1B-s> dosearch
1674     bind . <$M1B-equal> {incrfont 1}
1675     bind . <$M1B-KP_Add> {incrfont 1}
1676     bind . <$M1B-minus> {incrfont -1}
1677     bind . <$M1B-KP_Subtract> {incrfont -1}
1678     wm protocol . WM_DELETE_WINDOW doquit
1679     bind . <Button-1> "click %W"
1680     bind $fstring <Key-Return> {dofind 1 1}
1681     bind $sha1entry <Key-Return> gotocommit
1682     bind $sha1entry <<PasteSelection>> clearsha1
1683     bind $cflist <1> {sel_flist %W %x %y; break}
1684     bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
1685     bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
1686     bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
1688     set maincursor [. cget -cursor]
1689     set textcursor [$ctext cget -cursor]
1690     set curtextcursor $textcursor
1692     set rowctxmenu .rowctxmenu
1693     menu $rowctxmenu -tearoff 0
1694     $rowctxmenu add command -label "Diff this -> selected" \
1695         -command {diffvssel 0}
1696     $rowctxmenu add command -label "Diff selected -> this" \
1697         -command {diffvssel 1}
1698     $rowctxmenu add command -label "Make patch" -command mkpatch
1699     $rowctxmenu add command -label "Create tag" -command mktag
1700     $rowctxmenu add command -label "Write commit to file" -command writecommit
1701     $rowctxmenu add command -label "Create new branch" -command mkbranch
1702     $rowctxmenu add command -label "Cherry-pick this commit" \
1703         -command cherrypick
1704     $rowctxmenu add command -label "Reset HEAD branch to here" \
1705         -command resethead
1707     set fakerowmenu .fakerowmenu
1708     menu $fakerowmenu -tearoff 0
1709     $fakerowmenu add command -label "Diff this -> selected" \
1710         -command {diffvssel 0}
1711     $fakerowmenu add command -label "Diff selected -> this" \
1712         -command {diffvssel 1}
1713     $fakerowmenu add command -label "Make patch" -command mkpatch
1714 #    $fakerowmenu add command -label "Commit" -command {mkcommit 0}
1715 #    $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
1716 #    $fakerowmenu add command -label "Revert local changes" -command revertlocal
1718     set headctxmenu .headctxmenu
1719     menu $headctxmenu -tearoff 0
1720     $headctxmenu add command -label "Check out this branch" \
1721         -command cobranch
1722     $headctxmenu add command -label "Remove this branch" \
1723         -command rmbranch
1725     global flist_menu
1726     set flist_menu .flistctxmenu
1727     menu $flist_menu -tearoff 0
1728     $flist_menu add command -label "Highlight this too" \
1729         -command {flist_hl 0}
1730     $flist_menu add command -label "Highlight this only" \
1731         -command {flist_hl 1}
1734 # Windows sends all mouse wheel events to the current focused window, not
1735 # the one where the mouse hovers, so bind those events here and redirect
1736 # to the correct window
1737 proc windows_mousewheel_redirector {W X Y D} {
1738     global canv canv2 canv3
1739     set w [winfo containing -displayof $W $X $Y]
1740     if {$w ne ""} {
1741         set u [expr {$D < 0 ? 5 : -5}]
1742         if {$w == $canv || $w == $canv2 || $w == $canv3} {
1743             allcanvs yview scroll $u units
1744         } else {
1745             catch {
1746                 $w yview scroll $u units
1747             }
1748         }
1749     }
1752 # mouse-2 makes all windows scan vertically, but only the one
1753 # the cursor is in scans horizontally
1754 proc canvscan {op w x y} {
1755     global canv canv2 canv3
1756     foreach c [list $canv $canv2 $canv3] {
1757         if {$c == $w} {
1758             $c scan $op $x $y
1759         } else {
1760             $c scan $op 0 $y
1761         }
1762     }
1765 proc scrollcanv {cscroll f0 f1} {
1766     $cscroll set $f0 $f1
1767     drawfrac $f0 $f1
1768     flushhighlights
1771 # when we make a key binding for the toplevel, make sure
1772 # it doesn't get triggered when that key is pressed in the
1773 # find string entry widget.
1774 proc bindkey {ev script} {
1775     global entries
1776     bind . $ev $script
1777     set escript [bind Entry $ev]
1778     if {$escript == {}} {
1779         set escript [bind Entry <Key>]
1780     }
1781     foreach e $entries {
1782         bind $e $ev "$escript; break"
1783     }
1786 # set the focus back to the toplevel for any click outside
1787 # the entry widgets
1788 proc click {w} {
1789     global ctext entries
1790     foreach e [concat $entries $ctext] {
1791         if {$w == $e} return
1792     }
1793     focus .
1796 # Adjust the progress bar for a change in requested extent or canvas size
1797 proc adjustprogress {} {
1798     global progresscanv progressitem progresscoords
1799     global fprogitem fprogcoord lastprogupdate progupdatepending
1800     global rprogitem rprogcoord
1802     set w [expr {[winfo width $progresscanv] - 4}]
1803     set x0 [expr {$w * [lindex $progresscoords 0]}]
1804     set x1 [expr {$w * [lindex $progresscoords 1]}]
1805     set h [winfo height $progresscanv]
1806     $progresscanv coords $progressitem $x0 0 $x1 $h
1807     $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
1808     $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
1809     set now [clock clicks -milliseconds]
1810     if {$now >= $lastprogupdate + 100} {
1811         set progupdatepending 0
1812         update
1813     } elseif {!$progupdatepending} {
1814         set progupdatepending 1
1815         after [expr {$lastprogupdate + 100 - $now}] doprogupdate
1816     }
1819 proc doprogupdate {} {
1820     global lastprogupdate progupdatepending
1822     if {$progupdatepending} {
1823         set progupdatepending 0
1824         set lastprogupdate [clock clicks -milliseconds]
1825         update
1826     }
1829 proc savestuff {w} {
1830     global canv canv2 canv3 mainfont textfont uifont tabstop
1831     global stuffsaved findmergefiles maxgraphpct
1832     global maxwidth showneartags showlocalchanges
1833     global viewname viewfiles viewargs viewperm nextviewnum
1834     global cmitmode wrapcomment datetimeformat limitdiffs
1835     global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
1837     if {$stuffsaved} return
1838     if {![winfo viewable .]} return
1839     catch {
1840         set f [open "~/.gitk-new" w]
1841         puts $f [list set mainfont $mainfont]
1842         puts $f [list set textfont $textfont]
1843         puts $f [list set uifont $uifont]
1844         puts $f [list set tabstop $tabstop]
1845         puts $f [list set findmergefiles $findmergefiles]
1846         puts $f [list set maxgraphpct $maxgraphpct]
1847         puts $f [list set maxwidth $maxwidth]
1848         puts $f [list set cmitmode $cmitmode]
1849         puts $f [list set wrapcomment $wrapcomment]
1850         puts $f [list set showneartags $showneartags]
1851         puts $f [list set showlocalchanges $showlocalchanges]
1852         puts $f [list set datetimeformat $datetimeformat]
1853         puts $f [list set limitdiffs $limitdiffs]
1854         puts $f [list set bgcolor $bgcolor]
1855         puts $f [list set fgcolor $fgcolor]
1856         puts $f [list set colors $colors]
1857         puts $f [list set diffcolors $diffcolors]
1858         puts $f [list set diffcontext $diffcontext]
1859         puts $f [list set selectbgcolor $selectbgcolor]
1861         puts $f "set geometry(main) [wm geometry .]"
1862         puts $f "set geometry(topwidth) [winfo width .tf]"
1863         puts $f "set geometry(topheight) [winfo height .tf]"
1864         puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
1865         puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
1866         puts $f "set geometry(botwidth) [winfo width .bleft]"
1867         puts $f "set geometry(botheight) [winfo height .bleft]"
1869         puts -nonewline $f "set permviews {"
1870         for {set v 0} {$v < $nextviewnum} {incr v} {
1871             if {$viewperm($v)} {
1872                 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
1873             }
1874         }
1875         puts $f "}"
1876         close $f
1877         file rename -force "~/.gitk-new" "~/.gitk"
1878     }
1879     set stuffsaved 1
1882 proc resizeclistpanes {win w} {
1883     global oldwidth
1884     if {[info exists oldwidth($win)]} {
1885         set s0 [$win sash coord 0]
1886         set s1 [$win sash coord 1]
1887         if {$w < 60} {
1888             set sash0 [expr {int($w/2 - 2)}]
1889             set sash1 [expr {int($w*5/6 - 2)}]
1890         } else {
1891             set factor [expr {1.0 * $w / $oldwidth($win)}]
1892             set sash0 [expr {int($factor * [lindex $s0 0])}]
1893             set sash1 [expr {int($factor * [lindex $s1 0])}]
1894             if {$sash0 < 30} {
1895                 set sash0 30
1896             }
1897             if {$sash1 < $sash0 + 20} {
1898                 set sash1 [expr {$sash0 + 20}]
1899             }
1900             if {$sash1 > $w - 10} {
1901                 set sash1 [expr {$w - 10}]
1902                 if {$sash0 > $sash1 - 20} {
1903                     set sash0 [expr {$sash1 - 20}]
1904                 }
1905             }
1906         }
1907         $win sash place 0 $sash0 [lindex $s0 1]
1908         $win sash place 1 $sash1 [lindex $s1 1]
1909     }
1910     set oldwidth($win) $w
1913 proc resizecdetpanes {win w} {
1914     global oldwidth
1915     if {[info exists oldwidth($win)]} {
1916         set s0 [$win sash coord 0]
1917         if {$w < 60} {
1918             set sash0 [expr {int($w*3/4 - 2)}]
1919         } else {
1920             set factor [expr {1.0 * $w / $oldwidth($win)}]
1921             set sash0 [expr {int($factor * [lindex $s0 0])}]
1922             if {$sash0 < 45} {
1923                 set sash0 45
1924             }
1925             if {$sash0 > $w - 15} {
1926                 set sash0 [expr {$w - 15}]
1927             }
1928         }
1929         $win sash place 0 $sash0 [lindex $s0 1]
1930     }
1931     set oldwidth($win) $w
1934 proc allcanvs args {
1935     global canv canv2 canv3
1936     eval $canv $args
1937     eval $canv2 $args
1938     eval $canv3 $args
1941 proc bindall {event action} {
1942     global canv canv2 canv3
1943     bind $canv $event $action
1944     bind $canv2 $event $action
1945     bind $canv3 $event $action
1948 proc about {} {
1949     global uifont
1950     set w .about
1951     if {[winfo exists $w]} {
1952         raise $w
1953         return
1954     }
1955     toplevel $w
1956     wm title $w "About gitk"
1957     message $w.m -text {
1958 Gitk - a commit viewer for git
1960 Copyright Â© 2005-2007 Paul Mackerras
1962 Use and redistribute under the terms of the GNU General Public License} \
1963             -justify center -aspect 400 -border 2 -bg white -relief groove
1964     pack $w.m -side top -fill x -padx 2 -pady 2
1965     $w.m configure -font uifont
1966     button $w.ok -text Close -command "destroy $w" -default active
1967     pack $w.ok -side bottom
1968     $w.ok configure -font uifont
1969     bind $w <Visibility> "focus $w.ok"
1970     bind $w <Key-Escape> "destroy $w"
1971     bind $w <Key-Return> "destroy $w"
1974 proc keys {} {
1975     global uifont
1976     set w .keys
1977     if {[winfo exists $w]} {
1978         raise $w
1979         return
1980     }
1981     if {[tk windowingsystem] eq {aqua}} {
1982         set M1T Cmd
1983     } else {
1984         set M1T Ctrl
1985     }
1986     toplevel $w
1987     wm title $w "Gitk key bindings"
1988     message $w.m -text "
1989 Gitk key bindings:
1991 <$M1T-Q>                Quit
1992 <Home>          Move to first commit
1993 <End>           Move to last commit
1994 <Up>, p, i      Move up one commit
1995 <Down>, n, k    Move down one commit
1996 <Left>, z, j    Go back in history list
1997 <Right>, x, l   Go forward in history list
1998 <PageUp>        Move up one page in commit list
1999 <PageDown>      Move down one page in commit list
2000 <$M1T-Home>     Scroll to top of commit list
2001 <$M1T-End>      Scroll to bottom of commit list
2002 <$M1T-Up>       Scroll commit list up one line
2003 <$M1T-Down>     Scroll commit list down one line
2004 <$M1T-PageUp>   Scroll commit list up one page
2005 <$M1T-PageDown> Scroll commit list down one page
2006 <Shift-Up>      Find backwards (upwards, later commits)
2007 <Shift-Down>    Find forwards (downwards, earlier commits)
2008 <Delete>, b     Scroll diff view up one page
2009 <Backspace>     Scroll diff view up one page
2010 <Space>         Scroll diff view down one page
2011 u               Scroll diff view up 18 lines
2012 d               Scroll diff view down 18 lines
2013 <$M1T-F>                Find
2014 <$M1T-G>                Move to next find hit
2015 <Return>        Move to next find hit
2016 /               Move to next find hit, or redo find
2017 ?               Move to previous find hit
2018 f               Scroll diff view to next file
2019 <$M1T-S>                Search for next hit in diff view
2020 <$M1T-R>                Search for previous hit in diff view
2021 <$M1T-KP+>      Increase font size
2022 <$M1T-plus>     Increase font size
2023 <$M1T-KP->      Decrease font size
2024 <$M1T-minus>    Decrease font size
2025 <F5>            Update
2026 " \
2027             -justify left -bg white -border 2 -relief groove
2028     pack $w.m -side top -fill both -padx 2 -pady 2
2029     $w.m configure -font uifont
2030     button $w.ok -text Close -command "destroy $w" -default active
2031     pack $w.ok -side bottom
2032     $w.ok configure -font uifont
2033     bind $w <Visibility> "focus $w.ok"
2034     bind $w <Key-Escape> "destroy $w"
2035     bind $w <Key-Return> "destroy $w"
2038 # Procedures for manipulating the file list window at the
2039 # bottom right of the overall window.
2041 proc treeview {w l openlevs} {
2042     global treecontents treediropen treeheight treeparent treeindex
2044     set ix 0
2045     set treeindex() 0
2046     set lev 0
2047     set prefix {}
2048     set prefixend -1
2049     set prefendstack {}
2050     set htstack {}
2051     set ht 0
2052     set treecontents() {}
2053     $w conf -state normal
2054     foreach f $l {
2055         while {[string range $f 0 $prefixend] ne $prefix} {
2056             if {$lev <= $openlevs} {
2057                 $w mark set e:$treeindex($prefix) "end -1c"
2058                 $w mark gravity e:$treeindex($prefix) left
2059             }
2060             set treeheight($prefix) $ht
2061             incr ht [lindex $htstack end]
2062             set htstack [lreplace $htstack end end]
2063             set prefixend [lindex $prefendstack end]
2064             set prefendstack [lreplace $prefendstack end end]
2065             set prefix [string range $prefix 0 $prefixend]
2066             incr lev -1
2067         }
2068         set tail [string range $f [expr {$prefixend+1}] end]
2069         while {[set slash [string first "/" $tail]] >= 0} {
2070             lappend htstack $ht
2071             set ht 0
2072             lappend prefendstack $prefixend
2073             incr prefixend [expr {$slash + 1}]
2074             set d [string range $tail 0 $slash]
2075             lappend treecontents($prefix) $d
2076             set oldprefix $prefix
2077             append prefix $d
2078             set treecontents($prefix) {}
2079             set treeindex($prefix) [incr ix]
2080             set treeparent($prefix) $oldprefix
2081             set tail [string range $tail [expr {$slash+1}] end]
2082             if {$lev <= $openlevs} {
2083                 set ht 1
2084                 set treediropen($prefix) [expr {$lev < $openlevs}]
2085                 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2086                 $w mark set d:$ix "end -1c"
2087                 $w mark gravity d:$ix left
2088                 set str "\n"
2089                 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2090                 $w insert end $str
2091                 $w image create end -align center -image $bm -padx 1 \
2092                     -name a:$ix
2093                 $w insert end $d [highlight_tag $prefix]
2094                 $w mark set s:$ix "end -1c"
2095                 $w mark gravity s:$ix left
2096             }
2097             incr lev
2098         }
2099         if {$tail ne {}} {
2100             if {$lev <= $openlevs} {
2101                 incr ht
2102                 set str "\n"
2103                 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2104                 $w insert end $str
2105                 $w insert end $tail [highlight_tag $f]
2106             }
2107             lappend treecontents($prefix) $tail
2108         }
2109     }
2110     while {$htstack ne {}} {
2111         set treeheight($prefix) $ht
2112         incr ht [lindex $htstack end]
2113         set htstack [lreplace $htstack end end]
2114         set prefixend [lindex $prefendstack end]
2115         set prefendstack [lreplace $prefendstack end end]
2116         set prefix [string range $prefix 0 $prefixend]
2117     }
2118     $w conf -state disabled
2121 proc linetoelt {l} {
2122     global treeheight treecontents
2124     set y 2
2125     set prefix {}
2126     while {1} {
2127         foreach e $treecontents($prefix) {
2128             if {$y == $l} {
2129                 return "$prefix$e"
2130             }
2131             set n 1
2132             if {[string index $e end] eq "/"} {
2133                 set n $treeheight($prefix$e)
2134                 if {$y + $n > $l} {
2135                     append prefix $e
2136                     incr y
2137                     break
2138                 }
2139             }
2140             incr y $n
2141         }
2142     }
2145 proc highlight_tree {y prefix} {
2146     global treeheight treecontents cflist
2148     foreach e $treecontents($prefix) {
2149         set path $prefix$e
2150         if {[highlight_tag $path] ne {}} {
2151             $cflist tag add bold $y.0 "$y.0 lineend"
2152         }
2153         incr y
2154         if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2155             set y [highlight_tree $y $path]
2156         }
2157     }
2158     return $y
2161 proc treeclosedir {w dir} {
2162     global treediropen treeheight treeparent treeindex
2164     set ix $treeindex($dir)
2165     $w conf -state normal
2166     $w delete s:$ix e:$ix
2167     set treediropen($dir) 0
2168     $w image configure a:$ix -image tri-rt
2169     $w conf -state disabled
2170     set n [expr {1 - $treeheight($dir)}]
2171     while {$dir ne {}} {
2172         incr treeheight($dir) $n
2173         set dir $treeparent($dir)
2174     }
2177 proc treeopendir {w dir} {
2178     global treediropen treeheight treeparent treecontents treeindex
2180     set ix $treeindex($dir)
2181     $w conf -state normal
2182     $w image configure a:$ix -image tri-dn
2183     $w mark set e:$ix s:$ix
2184     $w mark gravity e:$ix right
2185     set lev 0
2186     set str "\n"
2187     set n [llength $treecontents($dir)]
2188     for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2189         incr lev
2190         append str "\t"
2191         incr treeheight($x) $n
2192     }
2193     foreach e $treecontents($dir) {
2194         set de $dir$e
2195         if {[string index $e end] eq "/"} {
2196             set iy $treeindex($de)
2197             $w mark set d:$iy e:$ix
2198             $w mark gravity d:$iy left
2199             $w insert e:$ix $str
2200             set treediropen($de) 0
2201             $w image create e:$ix -align center -image tri-rt -padx 1 \
2202                 -name a:$iy
2203             $w insert e:$ix $e [highlight_tag $de]
2204             $w mark set s:$iy e:$ix
2205             $w mark gravity s:$iy left
2206             set treeheight($de) 1
2207         } else {
2208             $w insert e:$ix $str
2209             $w insert e:$ix $e [highlight_tag $de]
2210         }
2211     }
2212     $w mark gravity e:$ix left
2213     $w conf -state disabled
2214     set treediropen($dir) 1
2215     set top [lindex [split [$w index @0,0] .] 0]
2216     set ht [$w cget -height]
2217     set l [lindex [split [$w index s:$ix] .] 0]
2218     if {$l < $top} {
2219         $w yview $l.0
2220     } elseif {$l + $n + 1 > $top + $ht} {
2221         set top [expr {$l + $n + 2 - $ht}]
2222         if {$l < $top} {
2223             set top $l
2224         }
2225         $w yview $top.0
2226     }
2229 proc treeclick {w x y} {
2230     global treediropen cmitmode ctext cflist cflist_top
2232     if {$cmitmode ne "tree"} return
2233     if {![info exists cflist_top]} return
2234     set l [lindex [split [$w index "@$x,$y"] "."] 0]
2235     $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2236     $cflist tag add highlight $l.0 "$l.0 lineend"
2237     set cflist_top $l
2238     if {$l == 1} {
2239         $ctext yview 1.0
2240         return
2241     }
2242     set e [linetoelt $l]
2243     if {[string index $e end] ne "/"} {
2244         showfile $e
2245     } elseif {$treediropen($e)} {
2246         treeclosedir $w $e
2247     } else {
2248         treeopendir $w $e
2249     }
2252 proc setfilelist {id} {
2253     global treefilelist cflist
2255     treeview $cflist $treefilelist($id) 0
2258 image create bitmap tri-rt -background black -foreground blue -data {
2259     #define tri-rt_width 13
2260     #define tri-rt_height 13
2261     static unsigned char tri-rt_bits[] = {
2262        0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2263        0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2264        0x00, 0x00};
2265 } -maskdata {
2266     #define tri-rt-mask_width 13
2267     #define tri-rt-mask_height 13
2268     static unsigned char tri-rt-mask_bits[] = {
2269        0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2270        0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2271        0x08, 0x00};
2273 image create bitmap tri-dn -background black -foreground blue -data {
2274     #define tri-dn_width 13
2275     #define tri-dn_height 13
2276     static unsigned char tri-dn_bits[] = {
2277        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2278        0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2279        0x00, 0x00};
2280 } -maskdata {
2281     #define tri-dn-mask_width 13
2282     #define tri-dn-mask_height 13
2283     static unsigned char tri-dn-mask_bits[] = {
2284        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2285        0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2286        0x00, 0x00};
2289 image create bitmap reficon-T -background black -foreground yellow -data {
2290     #define tagicon_width 13
2291     #define tagicon_height 9
2292     static unsigned char tagicon_bits[] = {
2293        0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2294        0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2295 } -maskdata {
2296     #define tagicon-mask_width 13
2297     #define tagicon-mask_height 9
2298     static unsigned char tagicon-mask_bits[] = {
2299        0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2300        0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2302 set rectdata {
2303     #define headicon_width 13
2304     #define headicon_height 9
2305     static unsigned char headicon_bits[] = {
2306        0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2307        0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2309 set rectmask {
2310     #define headicon-mask_width 13
2311     #define headicon-mask_height 9
2312     static unsigned char headicon-mask_bits[] = {
2313        0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2314        0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2316 image create bitmap reficon-H -background black -foreground green \
2317     -data $rectdata -maskdata $rectmask
2318 image create bitmap reficon-o -background black -foreground "#ddddff" \
2319     -data $rectdata -maskdata $rectmask
2321 proc init_flist {first} {
2322     global cflist cflist_top difffilestart
2324     $cflist conf -state normal
2325     $cflist delete 0.0 end
2326     if {$first ne {}} {
2327         $cflist insert end $first
2328         set cflist_top 1
2329         $cflist tag add highlight 1.0 "1.0 lineend"
2330     } else {
2331         catch {unset cflist_top}
2332     }
2333     $cflist conf -state disabled
2334     set difffilestart {}
2337 proc highlight_tag {f} {
2338     global highlight_paths
2340     foreach p $highlight_paths {
2341         if {[string match $p $f]} {
2342             return "bold"
2343         }
2344     }
2345     return {}
2348 proc highlight_filelist {} {
2349     global cmitmode cflist
2351     $cflist conf -state normal
2352     if {$cmitmode ne "tree"} {
2353         set end [lindex [split [$cflist index end] .] 0]
2354         for {set l 2} {$l < $end} {incr l} {
2355             set line [$cflist get $l.0 "$l.0 lineend"]
2356             if {[highlight_tag $line] ne {}} {
2357                 $cflist tag add bold $l.0 "$l.0 lineend"
2358             }
2359         }
2360     } else {
2361         highlight_tree 2 {}
2362     }
2363     $cflist conf -state disabled
2366 proc unhighlight_filelist {} {
2367     global cflist
2369     $cflist conf -state normal
2370     $cflist tag remove bold 1.0 end
2371     $cflist conf -state disabled
2374 proc add_flist {fl} {
2375     global cflist
2377     $cflist conf -state normal
2378     foreach f $fl {
2379         $cflist insert end "\n"
2380         $cflist insert end $f [highlight_tag $f]
2381     }
2382     $cflist conf -state disabled
2385 proc sel_flist {w x y} {
2386     global ctext difffilestart cflist cflist_top cmitmode
2388     if {$cmitmode eq "tree"} return
2389     if {![info exists cflist_top]} return
2390     set l [lindex [split [$w index "@$x,$y"] "."] 0]
2391     $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2392     $cflist tag add highlight $l.0 "$l.0 lineend"
2393     set cflist_top $l
2394     if {$l == 1} {
2395         $ctext yview 1.0
2396     } else {
2397         catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2398     }
2401 proc pop_flist_menu {w X Y x y} {
2402     global ctext cflist cmitmode flist_menu flist_menu_file
2403     global treediffs diffids
2405     stopfinding
2406     set l [lindex [split [$w index "@$x,$y"] "."] 0]
2407     if {$l <= 1} return
2408     if {$cmitmode eq "tree"} {
2409         set e [linetoelt $l]
2410         if {[string index $e end] eq "/"} return
2411     } else {
2412         set e [lindex $treediffs($diffids) [expr {$l-2}]]
2413     }
2414     set flist_menu_file $e
2415     tk_popup $flist_menu $X $Y
2418 proc flist_hl {only} {
2419     global flist_menu_file findstring gdttype
2421     set x [shellquote $flist_menu_file]
2422     if {$only || $findstring eq {} || $gdttype ne "touching paths:"} {
2423         set findstring $x
2424     } else {
2425         append findstring " " $x
2426     }
2427     set gdttype "touching paths:"
2430 # Functions for adding and removing shell-type quoting
2432 proc shellquote {str} {
2433     if {![string match "*\['\"\\ \t]*" $str]} {
2434         return $str
2435     }
2436     if {![string match "*\['\"\\]*" $str]} {
2437         return "\"$str\""
2438     }
2439     if {![string match "*'*" $str]} {
2440         return "'$str'"
2441     }
2442     return "\"[string map {\" \\\" \\ \\\\} $str]\""
2445 proc shellarglist {l} {
2446     set str {}
2447     foreach a $l {
2448         if {$str ne {}} {
2449             append str " "
2450         }
2451         append str [shellquote $a]
2452     }
2453     return $str
2456 proc shelldequote {str} {
2457     set ret {}
2458     set used -1
2459     while {1} {
2460         incr used
2461         if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
2462             append ret [string range $str $used end]
2463             set used [string length $str]
2464             break
2465         }
2466         set first [lindex $first 0]
2467         set ch [string index $str $first]
2468         if {$first > $used} {
2469             append ret [string range $str $used [expr {$first - 1}]]
2470             set used $first
2471         }
2472         if {$ch eq " " || $ch eq "\t"} break
2473         incr used
2474         if {$ch eq "'"} {
2475             set first [string first "'" $str $used]
2476             if {$first < 0} {
2477                 error "unmatched single-quote"
2478             }
2479             append ret [string range $str $used [expr {$first - 1}]]
2480             set used $first
2481             continue
2482         }
2483         if {$ch eq "\\"} {
2484             if {$used >= [string length $str]} {
2485                 error "trailing backslash"
2486             }
2487             append ret [string index $str $used]
2488             continue
2489         }
2490         # here ch == "\""
2491         while {1} {
2492             if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
2493                 error "unmatched double-quote"
2494             }
2495             set first [lindex $first 0]
2496             set ch [string index $str $first]
2497             if {$first > $used} {
2498                 append ret [string range $str $used [expr {$first - 1}]]
2499                 set used $first
2500             }
2501             if {$ch eq "\""} break
2502             incr used
2503             append ret [string index $str $used]
2504             incr used
2505         }
2506     }
2507     return [list $used $ret]
2510 proc shellsplit {str} {
2511     set l {}
2512     while {1} {
2513         set str [string trimleft $str]
2514         if {$str eq {}} break
2515         set dq [shelldequote $str]
2516         set n [lindex $dq 0]
2517         set word [lindex $dq 1]
2518         set str [string range $str $n end]
2519         lappend l $word
2520     }
2521     return $l
2524 # Code to implement multiple views
2526 proc newview {ishighlight} {
2527     global nextviewnum newviewname newviewperm uifont newishighlight
2528     global newviewargs revtreeargs
2530     set newishighlight $ishighlight
2531     set top .gitkview
2532     if {[winfo exists $top]} {
2533         raise $top
2534         return
2535     }
2536     set newviewname($nextviewnum) "View $nextviewnum"
2537     set newviewperm($nextviewnum) 0
2538     set newviewargs($nextviewnum) [shellarglist $revtreeargs]
2539     vieweditor $top $nextviewnum "Gitk view definition"
2542 proc editview {} {
2543     global curview
2544     global viewname viewperm newviewname newviewperm
2545     global viewargs newviewargs
2547     set top .gitkvedit-$curview
2548     if {[winfo exists $top]} {
2549         raise $top
2550         return
2551     }
2552     set newviewname($curview) $viewname($curview)
2553     set newviewperm($curview) $viewperm($curview)
2554     set newviewargs($curview) [shellarglist $viewargs($curview)]
2555     vieweditor $top $curview "Gitk: edit view $viewname($curview)"
2558 proc vieweditor {top n title} {
2559     global newviewname newviewperm viewfiles
2560     global uifont
2562     toplevel $top
2563     wm title $top $title
2564     label $top.nl -text "Name" -font uifont
2565     entry $top.name -width 20 -textvariable newviewname($n) -font uifont
2566     grid $top.nl $top.name -sticky w -pady 5
2567     checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
2568         -font uifont
2569     grid $top.perm - -pady 5 -sticky w
2570     message $top.al -aspect 1000 -font uifont \
2571         -text "Commits to include (arguments to git rev-list):"
2572     grid $top.al - -sticky w -pady 5
2573     entry $top.args -width 50 -textvariable newviewargs($n) \
2574         -background white -font uifont
2575     grid $top.args - -sticky ew -padx 5
2576     message $top.l -aspect 1000 -font uifont \
2577         -text "Enter files and directories to include, one per line:"
2578     grid $top.l - -sticky w
2579     text $top.t -width 40 -height 10 -background white -font uifont
2580     if {[info exists viewfiles($n)]} {
2581         foreach f $viewfiles($n) {
2582             $top.t insert end $f
2583             $top.t insert end "\n"
2584         }
2585         $top.t delete {end - 1c} end
2586         $top.t mark set insert 0.0
2587     }
2588     grid $top.t - -sticky ew -padx 5
2589     frame $top.buts
2590     button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
2591         -font uifont
2592     button $top.buts.can -text "Cancel" -command [list destroy $top] \
2593         -font uifont
2594     grid $top.buts.ok $top.buts.can
2595     grid columnconfigure $top.buts 0 -weight 1 -uniform a
2596     grid columnconfigure $top.buts 1 -weight 1 -uniform a
2597     grid $top.buts - -pady 10 -sticky ew
2598     focus $top.t
2601 proc doviewmenu {m first cmd op argv} {
2602     set nmenu [$m index end]
2603     for {set i $first} {$i <= $nmenu} {incr i} {
2604         if {[$m entrycget $i -command] eq $cmd} {
2605             eval $m $op $i $argv
2606             break
2607         }
2608     }
2611 proc allviewmenus {n op args} {
2612     # global viewhlmenu
2614     doviewmenu .bar.view 5 [list showview $n] $op $args
2615     # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
2618 proc newviewok {top n} {
2619     global nextviewnum newviewperm newviewname newishighlight
2620     global viewname viewfiles viewperm selectedview curview
2621     global viewargs newviewargs viewhlmenu
2623     if {[catch {
2624         set newargs [shellsplit $newviewargs($n)]
2625     } err]} {
2626         error_popup "Error in commit selection arguments: $err"
2627         wm raise $top
2628         focus $top
2629         return
2630     }
2631     set files {}
2632     foreach f [split [$top.t get 0.0 end] "\n"] {
2633         set ft [string trim $f]
2634         if {$ft ne {}} {
2635             lappend files $ft
2636         }
2637     }
2638     if {![info exists viewfiles($n)]} {
2639         # creating a new view
2640         incr nextviewnum
2641         set viewname($n) $newviewname($n)
2642         set viewperm($n) $newviewperm($n)
2643         set viewfiles($n) $files
2644         set viewargs($n) $newargs
2645         addviewmenu $n
2646         if {!$newishighlight} {
2647             run showview $n
2648         } else {
2649             run addvhighlight $n
2650         }
2651     } else {
2652         # editing an existing view
2653         set viewperm($n) $newviewperm($n)
2654         if {$newviewname($n) ne $viewname($n)} {
2655             set viewname($n) $newviewname($n)
2656             doviewmenu .bar.view 5 [list showview $n] \
2657                 entryconf [list -label $viewname($n)]
2658             # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
2659                 # entryconf [list -label $viewname($n) -value $viewname($n)]
2660         }
2661         if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
2662             set viewfiles($n) $files
2663             set viewargs($n) $newargs
2664             if {$curview == $n} {
2665                 run reloadcommits
2666             }
2667         }
2668     }
2669     catch {destroy $top}
2672 proc delview {} {
2673     global curview viewperm hlview selectedhlview
2675     if {$curview == 0} return
2676     if {[info exists hlview] && $hlview == $curview} {
2677         set selectedhlview None
2678         unset hlview
2679     }
2680     allviewmenus $curview delete
2681     set viewperm($curview) 0
2682     showview 0
2685 proc addviewmenu {n} {
2686     global viewname viewhlmenu
2688     .bar.view add radiobutton -label $viewname($n) \
2689         -command [list showview $n] -variable selectedview -value $n
2690     #$viewhlmenu add radiobutton -label $viewname($n) \
2691     #   -command [list addvhighlight $n] -variable selectedhlview
2694 proc showview {n} {
2695     global curview viewfiles cached_commitrow ordertok
2696     global displayorder parentlist rowidlist rowisopt rowfinal
2697     global colormap rowtextx nextcolor canvxmax
2698     global numcommits viewcomplete
2699     global selectedline currentid canv canvy0
2700     global treediffs
2701     global pending_select
2702     global commitidx
2703     global selectedview selectfirst
2704     global hlview selectedhlview commitinterest
2706     if {$n == $curview} return
2707     set selid {}
2708     set ymax [lindex [$canv cget -scrollregion] 3]
2709     set span [$canv yview]
2710     set ytop [expr {[lindex $span 0] * $ymax}]
2711     set ybot [expr {[lindex $span 1] * $ymax}]
2712     set yscreen [expr {($ybot - $ytop) / 2}]
2713     if {[info exists selectedline]} {
2714         set selid $currentid
2715         set y [yc $selectedline]
2716         if {$ytop < $y && $y < $ybot} {
2717             set yscreen [expr {$y - $ytop}]
2718         }
2719     } elseif {[info exists pending_select]} {
2720         set selid $pending_select
2721         unset pending_select
2722     }
2723     unselectline
2724     normalline
2725     catch {unset treediffs}
2726     clear_display
2727     if {[info exists hlview] && $hlview == $n} {
2728         unset hlview
2729         set selectedhlview None
2730     }
2731     catch {unset commitinterest}
2732     catch {unset cached_commitrow}
2733     catch {unset ordertok}
2735     set curview $n
2736     set selectedview $n
2737     .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
2738     .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
2740     run refill_reflist
2741     if {![info exists viewcomplete($n)]} {
2742         if {$selid ne {}} {
2743             set pending_select $selid
2744         }
2745         getcommits
2746         return
2747     }
2749     set displayorder {}
2750     set parentlist {}
2751     set rowidlist {}
2752     set rowisopt {}
2753     set rowfinal {}
2754     set numcommits $commitidx($n)
2756     catch {unset colormap}
2757     catch {unset rowtextx}
2758     set nextcolor 0
2759     set canvxmax [$canv cget -width]
2760     set curview $n
2761     set row 0
2762     setcanvscroll
2763     set yf 0
2764     set row {}
2765     set selectfirst 0
2766     if {$selid ne {} && [commitinview $selid $n]} {
2767         set row [rowofcommit $selid]
2768         # try to get the selected row in the same position on the screen
2769         set ymax [lindex [$canv cget -scrollregion] 3]
2770         set ytop [expr {[yc $row] - $yscreen}]
2771         if {$ytop < 0} {
2772             set ytop 0
2773         }
2774         set yf [expr {$ytop * 1.0 / $ymax}]
2775     }
2776     allcanvs yview moveto $yf
2777     drawvisible
2778     if {$row ne {}} {
2779         selectline $row 0
2780     } elseif {$selid ne {}} {
2781         set pending_select $selid
2782     } else {
2783         set row [first_real_row]
2784         if {$row < $numcommits} {
2785             selectline $row 0
2786         } else {
2787             set selectfirst 1
2788         }
2789     }
2790     if {!$viewcomplete($n)} {
2791         if {$numcommits == 0} {
2792             show_status "Reading commits..."
2793         } else {
2794             run chewcommits $n
2795         }
2796     } elseif {$numcommits == 0} {
2797         show_status "No commits selected"
2798     }
2801 # Stuff relating to the highlighting facility
2803 proc ishighlighted {row} {
2804     global vhighlights fhighlights nhighlights rhighlights
2806     if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
2807         return $nhighlights($row)
2808     }
2809     if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
2810         return $vhighlights($row)
2811     }
2812     if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
2813         return $fhighlights($row)
2814     }
2815     if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
2816         return $rhighlights($row)
2817     }
2818     return 0
2821 proc bolden {row font} {
2822     global canv linehtag selectedline boldrows
2824     lappend boldrows $row
2825     $canv itemconf $linehtag($row) -font $font
2826     if {[info exists selectedline] && $row == $selectedline} {
2827         $canv delete secsel
2828         set t [eval $canv create rect [$canv bbox $linehtag($row)] \
2829                    -outline {{}} -tags secsel \
2830                    -fill [$canv cget -selectbackground]]
2831         $canv lower $t
2832     }
2835 proc bolden_name {row font} {
2836     global canv2 linentag selectedline boldnamerows
2838     lappend boldnamerows $row
2839     $canv2 itemconf $linentag($row) -font $font
2840     if {[info exists selectedline] && $row == $selectedline} {
2841         $canv2 delete secsel
2842         set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
2843                    -outline {{}} -tags secsel \
2844                    -fill [$canv2 cget -selectbackground]]
2845         $canv2 lower $t
2846     }
2849 proc unbolden {} {
2850     global boldrows
2852     set stillbold {}
2853     foreach row $boldrows {
2854         if {![ishighlighted $row]} {
2855             bolden $row mainfont
2856         } else {
2857             lappend stillbold $row
2858         }
2859     }
2860     set boldrows $stillbold
2863 proc addvhighlight {n} {
2864     global hlview viewcomplete curview vhl_done vhighlights commitidx
2866     if {[info exists hlview]} {
2867         delvhighlight
2868     }
2869     set hlview $n
2870     if {$n != $curview && ![info exists viewcomplete($n)]} {
2871         start_rev_list $n
2872     }
2873     set vhl_done $commitidx($hlview)
2874     if {$vhl_done > 0} {
2875         drawvisible
2876     }
2879 proc delvhighlight {} {
2880     global hlview vhighlights
2882     if {![info exists hlview]} return
2883     unset hlview
2884     catch {unset vhighlights}
2885     unbolden
2888 proc vhighlightmore {} {
2889     global hlview vhl_done commitidx vhighlights curview
2891     set max $commitidx($hlview)
2892     set vr [visiblerows]
2893     set r0 [lindex $vr 0]
2894     set r1 [lindex $vr 1]
2895     for {set i $vhl_done} {$i < $max} {incr i} {
2896         set id [commitonrow $i $hlview]
2897         if {[commitinview $id $curview]} {
2898             set row [rowofcommit $id]
2899             if {$r0 <= $row && $row <= $r1} {
2900                 if {![highlighted $row]} {
2901                     bolden $row mainfontbold
2902                 }
2903                 set vhighlights($row) 1
2904             }
2905         }
2906     }
2907     set vhl_done $max
2910 proc askvhighlight {row id} {
2911     global hlview vhighlights iddrawn
2913     if {[commitinview $id $hlview]} {
2914         if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2915             bolden $row mainfontbold
2916         }
2917         set vhighlights($row) 1
2918     } else {
2919         set vhighlights($row) 0
2920     }
2923 proc hfiles_change {} {
2924     global highlight_files filehighlight fhighlights fh_serial
2925     global highlight_paths gdttype
2927     if {[info exists filehighlight]} {
2928         # delete previous highlights
2929         catch {close $filehighlight}
2930         unset filehighlight
2931         catch {unset fhighlights}
2932         unbolden
2933         unhighlight_filelist
2934     }
2935     set highlight_paths {}
2936     after cancel do_file_hl $fh_serial
2937     incr fh_serial
2938     if {$highlight_files ne {}} {
2939         after 300 do_file_hl $fh_serial
2940     }
2943 proc gdttype_change {name ix op} {
2944     global gdttype highlight_files findstring findpattern
2946     stopfinding
2947     if {$findstring ne {}} {
2948         if {$gdttype eq "containing:"} {
2949             if {$highlight_files ne {}} {
2950                 set highlight_files {}
2951                 hfiles_change
2952             }
2953             findcom_change
2954         } else {
2955             if {$findpattern ne {}} {
2956                 set findpattern {}
2957                 findcom_change
2958             }
2959             set highlight_files $findstring
2960             hfiles_change
2961         }
2962         drawvisible
2963     }
2964     # enable/disable findtype/findloc menus too
2967 proc find_change {name ix op} {
2968     global gdttype findstring highlight_files
2970     stopfinding
2971     if {$gdttype eq "containing:"} {
2972         findcom_change
2973     } else {
2974         if {$highlight_files ne $findstring} {
2975             set highlight_files $findstring
2976             hfiles_change
2977         }
2978     }
2979     drawvisible
2982 proc findcom_change args {
2983     global nhighlights boldnamerows
2984     global findpattern findtype findstring gdttype
2986     stopfinding
2987     # delete previous highlights, if any
2988     foreach row $boldnamerows {
2989         bolden_name $row mainfont
2990     }
2991     set boldnamerows {}
2992     catch {unset nhighlights}
2993     unbolden
2994     unmarkmatches
2995     if {$gdttype ne "containing:" || $findstring eq {}} {
2996         set findpattern {}
2997     } elseif {$findtype eq "Regexp"} {
2998         set findpattern $findstring
2999     } else {
3000         set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3001                    $findstring]
3002         set findpattern "*$e*"
3003     }
3006 proc makepatterns {l} {
3007     set ret {}
3008     foreach e $l {
3009         set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3010         if {[string index $ee end] eq "/"} {
3011             lappend ret "$ee*"
3012         } else {
3013             lappend ret $ee
3014             lappend ret "$ee/*"
3015         }
3016     }
3017     return $ret
3020 proc do_file_hl {serial} {
3021     global highlight_files filehighlight highlight_paths gdttype fhl_list
3023     if {$gdttype eq "touching paths:"} {
3024         if {[catch {set paths [shellsplit $highlight_files]}]} return
3025         set highlight_paths [makepatterns $paths]
3026         highlight_filelist
3027         set gdtargs [concat -- $paths]
3028     } elseif {$gdttype eq "adding/removing string:"} {
3029         set gdtargs [list "-S$highlight_files"]
3030     } else {
3031         # must be "containing:", i.e. we're searching commit info
3032         return
3033     }
3034     set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3035     set filehighlight [open $cmd r+]
3036     fconfigure $filehighlight -blocking 0
3037     filerun $filehighlight readfhighlight
3038     set fhl_list {}
3039     drawvisible
3040     flushhighlights
3043 proc flushhighlights {} {
3044     global filehighlight fhl_list
3046     if {[info exists filehighlight]} {
3047         lappend fhl_list {}
3048         puts $filehighlight ""
3049         flush $filehighlight
3050     }
3053 proc askfilehighlight {row id} {
3054     global filehighlight fhighlights fhl_list
3056     lappend fhl_list $id
3057     set fhighlights($row) -1
3058     puts $filehighlight $id
3061 proc readfhighlight {} {
3062     global filehighlight fhighlights curview iddrawn
3063     global fhl_list find_dirn
3065     if {![info exists filehighlight]} {
3066         return 0
3067     }
3068     set nr 0
3069     while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
3070         set line [string trim $line]
3071         set i [lsearch -exact $fhl_list $line]
3072         if {$i < 0} continue
3073         for {set j 0} {$j < $i} {incr j} {
3074             set id [lindex $fhl_list $j]
3075             if {[commitinview $id $curview]} {
3076                 set fhighlights([rowofcommit $id]) 0
3077             }
3078         }
3079         set fhl_list [lrange $fhl_list [expr {$i+1}] end]
3080         if {$line eq {}} continue
3081         if {![commitinview $line $curview]} continue
3082         set row [rowofcommit $line]
3083         if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
3084             bolden $row mainfontbold
3085         }
3086         set fhighlights($row) 1
3087     }
3088     if {[eof $filehighlight]} {
3089         # strange...
3090         puts "oops, git diff-tree died"
3091         catch {close $filehighlight}
3092         unset filehighlight
3093         return 0
3094     }
3095     if {[info exists find_dirn]} {
3096         run findmore
3097     }
3098     return 1
3101 proc doesmatch {f} {
3102     global findtype findpattern
3104     if {$findtype eq "Regexp"} {
3105         return [regexp $findpattern $f]
3106     } elseif {$findtype eq "IgnCase"} {
3107         return [string match -nocase $findpattern $f]
3108     } else {
3109         return [string match $findpattern $f]
3110     }
3113 proc askfindhighlight {row id} {
3114     global nhighlights commitinfo iddrawn
3115     global findloc
3116     global markingmatches
3118     if {![info exists commitinfo($id)]} {
3119         getcommit $id
3120     }
3121     set info $commitinfo($id)
3122     set isbold 0
3123     set fldtypes {Headline Author Date Committer CDate Comments}
3124     foreach f $info ty $fldtypes {
3125         if {($findloc eq "All fields" || $findloc eq $ty) &&
3126             [doesmatch $f]} {
3127             if {$ty eq "Author"} {
3128                 set isbold 2
3129                 break
3130             }
3131             set isbold 1
3132         }
3133     }
3134     if {$isbold && [info exists iddrawn($id)]} {
3135         if {![ishighlighted $row]} {
3136             bolden $row mainfontbold
3137             if {$isbold > 1} {
3138                 bolden_name $row mainfontbold
3139             }
3140         }
3141         if {$markingmatches} {
3142             markrowmatches $row $id
3143         }
3144     }
3145     set nhighlights($row) $isbold
3148 proc markrowmatches {row id} {
3149     global canv canv2 linehtag linentag commitinfo findloc
3151     set headline [lindex $commitinfo($id) 0]
3152     set author [lindex $commitinfo($id) 1]
3153     $canv delete match$row
3154     $canv2 delete match$row
3155     if {$findloc eq "All fields" || $findloc eq "Headline"} {
3156         set m [findmatches $headline]
3157         if {$m ne {}} {
3158             markmatches $canv $row $headline $linehtag($row) $m \
3159                 [$canv itemcget $linehtag($row) -font] $row
3160         }
3161     }
3162     if {$findloc eq "All fields" || $findloc eq "Author"} {
3163         set m [findmatches $author]
3164         if {$m ne {}} {
3165             markmatches $canv2 $row $author $linentag($row) $m \
3166                 [$canv2 itemcget $linentag($row) -font] $row
3167         }
3168     }
3171 proc vrel_change {name ix op} {
3172     global highlight_related
3174     rhighlight_none
3175     if {$highlight_related ne "None"} {
3176         run drawvisible
3177     }
3180 # prepare for testing whether commits are descendents or ancestors of a
3181 proc rhighlight_sel {a} {
3182     global descendent desc_todo ancestor anc_todo
3183     global highlight_related rhighlights
3185     catch {unset descendent}
3186     set desc_todo [list $a]
3187     catch {unset ancestor}
3188     set anc_todo [list $a]
3189     if {$highlight_related ne "None"} {
3190         rhighlight_none
3191         run drawvisible
3192     }
3195 proc rhighlight_none {} {
3196     global rhighlights
3198     catch {unset rhighlights}
3199     unbolden
3202 proc is_descendent {a} {
3203     global curview children descendent desc_todo
3205     set v $curview
3206     set la [rowofcommit $a]
3207     set todo $desc_todo
3208     set leftover {}
3209     set done 0
3210     for {set i 0} {$i < [llength $todo]} {incr i} {
3211         set do [lindex $todo $i]
3212         if {[rowofcommit $do] < $la} {
3213             lappend leftover $do
3214             continue
3215         }
3216         foreach nk $children($v,$do) {
3217             if {![info exists descendent($nk)]} {
3218                 set descendent($nk) 1
3219                 lappend todo $nk
3220                 if {$nk eq $a} {
3221                     set done 1
3222                 }
3223             }
3224         }
3225         if {$done} {
3226             set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3227             return
3228         }
3229     }
3230     set descendent($a) 0
3231     set desc_todo $leftover
3234 proc is_ancestor {a} {
3235     global curview parents ancestor anc_todo
3237     set v $curview
3238     set la [rowofcommit $a]
3239     set todo $anc_todo
3240     set leftover {}
3241     set done 0
3242     for {set i 0} {$i < [llength $todo]} {incr i} {
3243         set do [lindex $todo $i]
3244         if {![commitinview $do $v] || [rowofcommit $do] > $la} {
3245             lappend leftover $do
3246             continue
3247         }
3248         foreach np $parents($v,$do) {
3249             if {![info exists ancestor($np)]} {
3250                 set ancestor($np) 1
3251                 lappend todo $np
3252                 if {$np eq $a} {
3253                     set done 1
3254                 }
3255             }
3256         }
3257         if {$done} {
3258             set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3259             return
3260         }
3261     }
3262     set ancestor($a) 0
3263     set anc_todo $leftover
3266 proc askrelhighlight {row id} {
3267     global descendent highlight_related iddrawn rhighlights
3268     global selectedline ancestor
3270     if {![info exists selectedline]} return
3271     set isbold 0
3272     if {$highlight_related eq "Descendent" ||
3273         $highlight_related eq "Not descendent"} {
3274         if {![info exists descendent($id)]} {
3275             is_descendent $id
3276         }
3277         if {$descendent($id) == ($highlight_related eq "Descendent")} {
3278             set isbold 1
3279         }
3280     } elseif {$highlight_related eq "Ancestor" ||
3281               $highlight_related eq "Not ancestor"} {
3282         if {![info exists ancestor($id)]} {
3283             is_ancestor $id
3284         }
3285         if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
3286             set isbold 1
3287         }
3288     }
3289     if {[info exists iddrawn($id)]} {
3290         if {$isbold && ![ishighlighted $row]} {
3291             bolden $row mainfontbold
3292         }
3293     }
3294     set rhighlights($row) $isbold
3297 # Graph layout functions
3299 proc shortids {ids} {
3300     set res {}
3301     foreach id $ids {
3302         if {[llength $id] > 1} {
3303             lappend res [shortids $id]
3304         } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
3305             lappend res [string range $id 0 7]
3306         } else {
3307             lappend res $id
3308         }
3309     }
3310     return $res
3313 proc ntimes {n o} {
3314     set ret {}
3315     set o [list $o]
3316     for {set mask 1} {$mask <= $n} {incr mask $mask} {
3317         if {($n & $mask) != 0} {
3318             set ret [concat $ret $o]
3319         }
3320         set o [concat $o $o]
3321     }
3322     return $ret
3325 proc ordertoken {id} {
3326     global ordertok curview varcid varcstart varctok curview parents children
3327     global nullid nullid2
3329     if {[info exists ordertok($id)]} {
3330         return $ordertok($id)
3331     }
3332     set origid $id
3333     set todo {}
3334     while {1} {
3335         if {[info exists varcid($curview,$id)]} {
3336             set a $varcid($curview,$id)
3337             set p [lindex $varcstart($curview) $a]
3338         } else {
3339             set p [lindex $children($curview,$id) 0]
3340         }
3341         if {[info exists ordertok($p)]} {
3342             set tok $ordertok($p)
3343             break
3344         }
3345         if {[llength $children($curview,$p)] == 0} {
3346             # it's a root
3347             set tok [lindex $varctok($curview) $a]
3348             break
3349         }
3350         set id [lindex $children($curview,$p) 0]
3351         if {$id eq $nullid || $id eq $nullid2} {
3352             # XXX treat it as a root
3353             set tok [lindex $varctok($curview) $a]
3354             break
3355         }
3356         if {[llength $parents($curview,$id)] == 1} {
3357             lappend todo [list $p {}]
3358         } else {
3359             set j [lsearch -exact $parents($curview,$id) $p]
3360             if {$j < 0} {
3361                 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
3362             }
3363             lappend todo [list $p [strrep $j]]
3364         }
3365     }
3366     for {set i [llength $todo]} {[incr i -1] >= 0} {} {
3367         set p [lindex $todo $i 0]
3368         append tok [lindex $todo $i 1]
3369         set ordertok($p) $tok
3370     }
3371     set ordertok($origid) $tok
3372     return $tok
3375 # Work out where id should go in idlist so that order-token
3376 # values increase from left to right
3377 proc idcol {idlist id {i 0}} {
3378     set t [ordertoken $id]
3379     if {$i < 0} {
3380         set i 0
3381     }
3382     if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
3383         if {$i > [llength $idlist]} {
3384             set i [llength $idlist]
3385         }
3386         while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
3387         incr i
3388     } else {
3389         if {$t > [ordertoken [lindex $idlist $i]]} {
3390             while {[incr i] < [llength $idlist] &&
3391                    $t >= [ordertoken [lindex $idlist $i]]} {}
3392         }
3393     }
3394     return $i
3397 proc initlayout {} {
3398     global rowidlist rowisopt rowfinal displayorder parentlist
3399     global numcommits canvxmax canv
3400     global nextcolor
3401     global colormap rowtextx
3402     global selectfirst
3404     set numcommits 0
3405     set displayorder {}
3406     set parentlist {}
3407     set nextcolor 0
3408     set rowidlist {}
3409     set rowisopt {}
3410     set rowfinal {}
3411     set canvxmax [$canv cget -width]
3412     catch {unset colormap}
3413     catch {unset rowtextx}
3414     set selectfirst 1
3417 proc setcanvscroll {} {
3418     global canv canv2 canv3 numcommits linespc canvxmax canvy0
3420     set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
3421     $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
3422     $canv2 conf -scrollregion [list 0 0 0 $ymax]
3423     $canv3 conf -scrollregion [list 0 0 0 $ymax]
3426 proc visiblerows {} {
3427     global canv numcommits linespc
3429     set ymax [lindex [$canv cget -scrollregion] 3]
3430     if {$ymax eq {} || $ymax == 0} return
3431     set f [$canv yview]
3432     set y0 [expr {int([lindex $f 0] * $ymax)}]
3433     set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
3434     if {$r0 < 0} {
3435         set r0 0
3436     }
3437     set y1 [expr {int([lindex $f 1] * $ymax)}]
3438     set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
3439     if {$r1 >= $numcommits} {
3440         set r1 [expr {$numcommits - 1}]
3441     }
3442     return [list $r0 $r1]
3445 proc layoutmore {} {
3446     global commitidx viewcomplete curview
3447     global numcommits pending_select selectedline curview
3448     global selectfirst lastscrollset commitinterest
3450     set canshow $commitidx($curview)
3451     if {$canshow <= $numcommits && !$viewcomplete($curview)} return
3452     if {$numcommits == 0} {
3453         allcanvs delete all
3454     }
3455     set r0 $numcommits
3456     set prev $numcommits
3457     set numcommits $canshow
3458     set t [clock clicks -milliseconds]
3459     if {$prev < 100 || $viewcomplete($curview) || $t - $lastscrollset > 500} {
3460         set lastscrollset $t
3461         setcanvscroll
3462     }
3463     set rows [visiblerows]
3464     set r1 [lindex $rows 1]
3465     if {$r1 >= $canshow} {
3466         set r1 [expr {$canshow - 1}]
3467     }
3468     if {$r0 <= $r1} {
3469         drawcommits $r0 $r1
3470     }
3471     if {[info exists pending_select] &&
3472         [commitinview $pending_select $curview]} {
3473         selectline [rowofcommit $pending_select] 1
3474     }
3475     if {$selectfirst} {
3476         if {[info exists selectedline] || [info exists pending_select]} {
3477             set selectfirst 0
3478         } else {
3479             set l [first_real_row]
3480             selectline $l 1
3481             set selectfirst 0
3482         }
3483     }
3486 proc doshowlocalchanges {} {
3487     global curview mainheadid
3489     if {[commitinview $mainheadid $curview]} {
3490         dodiffindex
3491     } else {
3492         lappend commitinterest($mainheadid) {dodiffindex}
3493     }
3496 proc dohidelocalchanges {} {
3497     global nullid nullid2 lserial curview
3499     if {[commitinview $nullid $curview]} {
3500         removerow $nullid $curview
3501     }
3502     if {[commitinview $nullid2 $curview]} {
3503         removerow $nullid2 $curview
3504     }
3505     incr lserial
3508 # spawn off a process to do git diff-index --cached HEAD
3509 proc dodiffindex {} {
3510     global lserial showlocalchanges
3512     if {!$showlocalchanges} return
3513     incr lserial
3514     set fd [open "|git diff-index --cached HEAD" r]
3515     fconfigure $fd -blocking 0
3516     filerun $fd [list readdiffindex $fd $lserial]
3519 proc readdiffindex {fd serial} {
3520     global mainheadid nullid2 curview commitinfo commitdata lserial
3522     set isdiff 1
3523     if {[gets $fd line] < 0} {
3524         if {![eof $fd]} {
3525             return 1
3526         }
3527         set isdiff 0
3528     }
3529     # we only need to see one line and we don't really care what it says...
3530     close $fd
3532     if {$serial != $lserial} {
3533         return 0
3534     }
3536     # now see if there are any local changes not checked in to the index
3537     set fd [open "|git diff-files" r]
3538     fconfigure $fd -blocking 0
3539     filerun $fd [list readdifffiles $fd $serial]
3541     if {$isdiff && ![commitinview $nullid2 $curview]} {
3542         # add the line for the changes in the index to the graph
3543         set hl "Local changes checked in to index but not committed"
3544         set commitinfo($nullid2) [list  $hl {} {} {} {} "    $hl\n"]
3545         set commitdata($nullid2) "\n    $hl\n"
3546         insertrow $nullid2 $mainheadid $curview
3547     } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
3548         removerow $nullid2 $curview
3549     }
3550     return 0
3553 proc readdifffiles {fd serial} {
3554     global mainheadid nullid nullid2 curview
3555     global commitinfo commitdata lserial
3557     set isdiff 1
3558     if {[gets $fd line] < 0} {
3559         if {![eof $fd]} {
3560             return 1
3561         }
3562         set isdiff 0
3563     }
3564     # we only need to see one line and we don't really care what it says...
3565     close $fd
3567     if {$serial != $lserial} {
3568         return 0
3569     }
3571     if {$isdiff && ![commitinview $nullid $curview]} {
3572         # add the line for the local diff to the graph
3573         set hl "Local uncommitted changes, not checked in to index"
3574         set commitinfo($nullid) [list  $hl {} {} {} {} "    $hl\n"]
3575         set commitdata($nullid) "\n    $hl\n"
3576         if {[commitinview $nullid2 $curview]} {
3577             set p $nullid2
3578         } else {
3579             set p $mainheadid
3580         }
3581         insertrow $nullid $p $curview
3582     } elseif {!$isdiff && [commitinview $nullid $curview]} {
3583         removerow $nullid $curview
3584     }
3585     return 0
3588 proc nextuse {id row} {
3589     global curview children
3591     if {[info exists children($curview,$id)]} {
3592         foreach kid $children($curview,$id) {
3593             if {![commitinview $kid $curview]} {
3594                 return -1
3595             }
3596             if {[rowofcommit $kid] > $row} {
3597                 return [rowofcommit $kid]
3598             }
3599         }
3600     }
3601     if {[commitinview $id $curview]} {
3602         return [rowofcommit $id]
3603     }
3604     return -1
3607 proc prevuse {id row} {
3608     global curview children
3610     set ret -1
3611     if {[info exists children($curview,$id)]} {
3612         foreach kid $children($curview,$id) {
3613             if {![commitinview $kid $curview]} break
3614             if {[rowofcommit $kid] < $row} {
3615                 set ret [rowofcommit $kid]
3616             }
3617         }
3618     }
3619     return $ret
3622 proc make_idlist {row} {
3623     global displayorder parentlist uparrowlen downarrowlen mingaplen
3624     global commitidx curview children
3626     set r [expr {$row - $mingaplen - $downarrowlen - 1}]
3627     if {$r < 0} {
3628         set r 0
3629     }
3630     set ra [expr {$row - $downarrowlen}]
3631     if {$ra < 0} {
3632         set ra 0
3633     }
3634     set rb [expr {$row + $uparrowlen}]
3635     if {$rb > $commitidx($curview)} {
3636         set rb $commitidx($curview)
3637     }
3638     make_disporder $r [expr {$rb + 1}]
3639     set ids {}
3640     for {} {$r < $ra} {incr r} {
3641         set nextid [lindex $displayorder [expr {$r + 1}]]
3642         foreach p [lindex $parentlist $r] {
3643             if {$p eq $nextid} continue
3644             set rn [nextuse $p $r]
3645             if {$rn >= $row &&
3646                 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
3647                 lappend ids [list [ordertoken $p] $p]
3648             }
3649         }
3650     }
3651     for {} {$r < $row} {incr r} {
3652         set nextid [lindex $displayorder [expr {$r + 1}]]
3653         foreach p [lindex $parentlist $r] {
3654             if {$p eq $nextid} continue
3655             set rn [nextuse $p $r]
3656             if {$rn < 0 || $rn >= $row} {
3657                 lappend ids [list [ordertoken $p] $p]
3658             }
3659         }
3660     }
3661     set id [lindex $displayorder $row]
3662     lappend ids [list [ordertoken $id] $id]
3663     while {$r < $rb} {
3664         foreach p [lindex $parentlist $r] {
3665             set firstkid [lindex $children($curview,$p) 0]
3666             if {[rowofcommit $firstkid] < $row} {
3667                 lappend ids [list [ordertoken $p] $p]
3668             }
3669         }
3670         incr r
3671         set id [lindex $displayorder $r]
3672         if {$id ne {}} {
3673             set firstkid [lindex $children($curview,$id) 0]
3674             if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
3675                 lappend ids [list [ordertoken $id] $id]
3676             }
3677         }
3678     }
3679     set idlist {}
3680     foreach idx [lsort -unique $ids] {
3681         lappend idlist [lindex $idx 1]
3682     }
3683     return $idlist
3686 proc rowsequal {a b} {
3687     while {[set i [lsearch -exact $a {}]] >= 0} {
3688         set a [lreplace $a $i $i]
3689     }
3690     while {[set i [lsearch -exact $b {}]] >= 0} {
3691         set b [lreplace $b $i $i]
3692     }
3693     return [expr {$a eq $b}]
3696 proc makeupline {id row rend col} {
3697     global rowidlist uparrowlen downarrowlen mingaplen
3699     for {set r $rend} {1} {set r $rstart} {
3700         set rstart [prevuse $id $r]
3701         if {$rstart < 0} return
3702         if {$rstart < $row} break
3703     }
3704     if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
3705         set rstart [expr {$rend - $uparrowlen - 1}]
3706     }
3707     for {set r $rstart} {[incr r] <= $row} {} {
3708         set idlist [lindex $rowidlist $r]
3709         if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
3710             set col [idcol $idlist $id $col]
3711             lset rowidlist $r [linsert $idlist $col $id]
3712             changedrow $r
3713         }
3714     }
3717 proc layoutrows {row endrow} {
3718     global rowidlist rowisopt rowfinal displayorder
3719     global uparrowlen downarrowlen maxwidth mingaplen
3720     global children parentlist
3721     global commitidx viewcomplete curview
3723     make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
3724     set idlist {}
3725     if {$row > 0} {
3726         set rm1 [expr {$row - 1}]
3727         foreach id [lindex $rowidlist $rm1] {
3728             if {$id ne {}} {
3729                 lappend idlist $id
3730             }
3731         }
3732         set final [lindex $rowfinal $rm1]
3733     }
3734     for {} {$row < $endrow} {incr row} {
3735         set rm1 [expr {$row - 1}]
3736         if {$rm1 < 0 || $idlist eq {}} {
3737             set idlist [make_idlist $row]
3738             set final 1
3739         } else {
3740             set id [lindex $displayorder $rm1]
3741             set col [lsearch -exact $idlist $id]
3742             set idlist [lreplace $idlist $col $col]
3743             foreach p [lindex $parentlist $rm1] {
3744                 if {[lsearch -exact $idlist $p] < 0} {
3745                     set col [idcol $idlist $p $col]
3746                     set idlist [linsert $idlist $col $p]
3747                     # if not the first child, we have to insert a line going up
3748                     if {$id ne [lindex $children($curview,$p) 0]} {
3749                         makeupline $p $rm1 $row $col
3750                     }
3751                 }
3752             }
3753             set id [lindex $displayorder $row]
3754             if {$row > $downarrowlen} {
3755                 set termrow [expr {$row - $downarrowlen - 1}]
3756                 foreach p [lindex $parentlist $termrow] {
3757                     set i [lsearch -exact $idlist $p]
3758                     if {$i < 0} continue
3759                     set nr [nextuse $p $termrow]
3760                     if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
3761                         set idlist [lreplace $idlist $i $i]
3762                     }
3763                 }
3764             }
3765             set col [lsearch -exact $idlist $id]
3766             if {$col < 0} {
3767                 set col [idcol $idlist $id]
3768                 set idlist [linsert $idlist $col $id]
3769                 if {$children($curview,$id) ne {}} {
3770                     makeupline $id $rm1 $row $col
3771                 }
3772             }
3773             set r [expr {$row + $uparrowlen - 1}]
3774             if {$r < $commitidx($curview)} {
3775                 set x $col
3776                 foreach p [lindex $parentlist $r] {
3777                     if {[lsearch -exact $idlist $p] >= 0} continue
3778                     set fk [lindex $children($curview,$p) 0]
3779                     if {[rowofcommit $fk] < $row} {
3780                         set x [idcol $idlist $p $x]
3781                         set idlist [linsert $idlist $x $p]
3782                     }
3783                 }
3784                 if {[incr r] < $commitidx($curview)} {
3785                     set p [lindex $displayorder $r]
3786                     if {[lsearch -exact $idlist $p] < 0} {
3787                         set fk [lindex $children($curview,$p) 0]
3788                         if {$fk ne {} && [rowofcommit $fk] < $row} {
3789                             set x [idcol $idlist $p $x]
3790                             set idlist [linsert $idlist $x $p]
3791                         }
3792                     }
3793                 }
3794             }
3795         }
3796         if {$final && !$viewcomplete($curview) &&
3797             $row + $uparrowlen + $mingaplen + $downarrowlen
3798                 >= $commitidx($curview)} {
3799             set final 0
3800         }
3801         set l [llength $rowidlist]
3802         if {$row == $l} {
3803             lappend rowidlist $idlist
3804             lappend rowisopt 0
3805             lappend rowfinal $final
3806         } elseif {$row < $l} {
3807             if {![rowsequal $idlist [lindex $rowidlist $row]]} {
3808                 lset rowidlist $row $idlist
3809                 changedrow $row
3810             }
3811             lset rowfinal $row $final
3812         } else {
3813             set pad [ntimes [expr {$row - $l}] {}]
3814             set rowidlist [concat $rowidlist $pad]
3815             lappend rowidlist $idlist
3816             set rowfinal [concat $rowfinal $pad]
3817             lappend rowfinal $final
3818             set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
3819         }
3820     }
3821     return $row
3824 proc changedrow {row} {
3825     global displayorder iddrawn rowisopt need_redisplay
3827     set l [llength $rowisopt]
3828     if {$row < $l} {
3829         lset rowisopt $row 0
3830         if {$row + 1 < $l} {
3831             lset rowisopt [expr {$row + 1}] 0
3832             if {$row + 2 < $l} {
3833                 lset rowisopt [expr {$row + 2}] 0
3834             }
3835         }
3836     }
3837     set id [lindex $displayorder $row]
3838     if {[info exists iddrawn($id)]} {
3839         set need_redisplay 1
3840     }
3843 proc insert_pad {row col npad} {
3844     global rowidlist
3846     set pad [ntimes $npad {}]
3847     set idlist [lindex $rowidlist $row]
3848     set bef [lrange $idlist 0 [expr {$col - 1}]]
3849     set aft [lrange $idlist $col end]
3850     set i [lsearch -exact $aft {}]
3851     if {$i > 0} {
3852         set aft [lreplace $aft $i $i]
3853     }
3854     lset rowidlist $row [concat $bef $pad $aft]
3855     changedrow $row
3858 proc optimize_rows {row col endrow} {
3859     global rowidlist rowisopt displayorder curview children
3861     if {$row < 1} {
3862         set row 1
3863     }
3864     for {} {$row < $endrow} {incr row; set col 0} {
3865         if {[lindex $rowisopt $row]} continue
3866         set haspad 0
3867         set y0 [expr {$row - 1}]
3868         set ym [expr {$row - 2}]
3869         set idlist [lindex $rowidlist $row]
3870         set previdlist [lindex $rowidlist $y0]
3871         if {$idlist eq {} || $previdlist eq {}} continue
3872         if {$ym >= 0} {
3873             set pprevidlist [lindex $rowidlist $ym]
3874             if {$pprevidlist eq {}} continue
3875         } else {
3876             set pprevidlist {}
3877         }
3878         set x0 -1
3879         set xm -1
3880         for {} {$col < [llength $idlist]} {incr col} {
3881             set id [lindex $idlist $col]
3882             if {[lindex $previdlist $col] eq $id} continue
3883             if {$id eq {}} {
3884                 set haspad 1
3885                 continue
3886             }
3887             set x0 [lsearch -exact $previdlist $id]
3888             if {$x0 < 0} continue
3889             set z [expr {$x0 - $col}]
3890             set isarrow 0
3891             set z0 {}
3892             if {$ym >= 0} {
3893                 set xm [lsearch -exact $pprevidlist $id]
3894                 if {$xm >= 0} {
3895                     set z0 [expr {$xm - $x0}]
3896                 }
3897             }
3898             if {$z0 eq {}} {
3899                 # if row y0 is the first child of $id then it's not an arrow
3900                 if {[lindex $children($curview,$id) 0] ne
3901                     [lindex $displayorder $y0]} {
3902                     set isarrow 1
3903                 }
3904             }
3905             if {!$isarrow && $id ne [lindex $displayorder $row] &&
3906                 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
3907                 set isarrow 1
3908             }
3909             # Looking at lines from this row to the previous row,
3910             # make them go straight up if they end in an arrow on
3911             # the previous row; otherwise make them go straight up
3912             # or at 45 degrees.
3913             if {$z < -1 || ($z < 0 && $isarrow)} {
3914                 # Line currently goes left too much;
3915                 # insert pads in the previous row, then optimize it
3916                 set npad [expr {-1 - $z + $isarrow}]
3917                 insert_pad $y0 $x0 $npad
3918                 if {$y0 > 0} {
3919                     optimize_rows $y0 $x0 $row
3920                 }
3921                 set previdlist [lindex $rowidlist $y0]
3922                 set x0 [lsearch -exact $previdlist $id]
3923                 set z [expr {$x0 - $col}]
3924                 if {$z0 ne {}} {
3925                     set pprevidlist [lindex $rowidlist $ym]
3926                     set xm [lsearch -exact $pprevidlist $id]
3927                     set z0 [expr {$xm - $x0}]
3928                 }
3929             } elseif {$z > 1 || ($z > 0 && $isarrow)} {
3930                 # Line currently goes right too much;
3931                 # insert pads in this line
3932                 set npad [expr {$z - 1 + $isarrow}]
3933                 insert_pad $row $col $npad
3934                 set idlist [lindex $rowidlist $row]
3935                 incr col $npad
3936                 set z [expr {$x0 - $col}]
3937                 set haspad 1
3938             }
3939             if {$z0 eq {} && !$isarrow && $ym >= 0} {
3940                 # this line links to its first child on row $row-2
3941                 set id [lindex $displayorder $ym]
3942                 set xc [lsearch -exact $pprevidlist $id]
3943                 if {$xc >= 0} {
3944                     set z0 [expr {$xc - $x0}]
3945                 }
3946             }
3947             # avoid lines jigging left then immediately right
3948             if {$z0 ne {} && $z < 0 && $z0 > 0} {
3949                 insert_pad $y0 $x0 1
3950                 incr x0
3951                 optimize_rows $y0 $x0 $row
3952                 set previdlist [lindex $rowidlist $y0]
3953             }
3954         }
3955         if {!$haspad} {
3956             # Find the first column that doesn't have a line going right
3957             for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3958                 set id [lindex $idlist $col]
3959                 if {$id eq {}} break
3960                 set x0 [lsearch -exact $previdlist $id]
3961                 if {$x0 < 0} {
3962                     # check if this is the link to the first child
3963                     set kid [lindex $displayorder $y0]
3964                     if {[lindex $children($curview,$id) 0] eq $kid} {
3965                         # it is, work out offset to child
3966                         set x0 [lsearch -exact $previdlist $kid]
3967                     }
3968                 }
3969                 if {$x0 <= $col} break
3970             }
3971             # Insert a pad at that column as long as it has a line and
3972             # isn't the last column
3973             if {$x0 >= 0 && [incr col] < [llength $idlist]} {
3974                 set idlist [linsert $idlist $col {}]
3975                 lset rowidlist $row $idlist
3976                 changedrow $row
3977             }
3978         }
3979     }
3982 proc xc {row col} {
3983     global canvx0 linespc
3984     return [expr {$canvx0 + $col * $linespc}]
3987 proc yc {row} {
3988     global canvy0 linespc
3989     return [expr {$canvy0 + $row * $linespc}]
3992 proc linewidth {id} {
3993     global thickerline lthickness
3995     set wid $lthickness
3996     if {[info exists thickerline] && $id eq $thickerline} {
3997         set wid [expr {2 * $lthickness}]
3998     }
3999     return $wid
4002 proc rowranges {id} {
4003     global curview children uparrowlen downarrowlen
4004     global rowidlist
4006     set kids $children($curview,$id)
4007     if {$kids eq {}} {
4008         return {}
4009     }
4010     set ret {}
4011     lappend kids $id
4012     foreach child $kids {
4013         if {![commitinview $child $curview]} break
4014         set row [rowofcommit $child]
4015         if {![info exists prev]} {
4016             lappend ret [expr {$row + 1}]
4017         } else {
4018             if {$row <= $prevrow} {
4019                 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4020             }
4021             # see if the line extends the whole way from prevrow to row
4022             if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4023                 [lsearch -exact [lindex $rowidlist \
4024                             [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4025                 # it doesn't, see where it ends
4026                 set r [expr {$prevrow + $downarrowlen}]
4027                 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4028                     while {[incr r -1] > $prevrow &&
4029                            [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4030                 } else {
4031                     while {[incr r] <= $row &&
4032                            [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4033                     incr r -1
4034                 }
4035                 lappend ret $r
4036                 # see where it starts up again
4037                 set r [expr {$row - $uparrowlen}]
4038                 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4039                     while {[incr r] < $row &&
4040                            [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4041                 } else {
4042                     while {[incr r -1] >= $prevrow &&
4043                            [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4044                     incr r
4045                 }
4046                 lappend ret $r
4047             }
4048         }
4049         if {$child eq $id} {
4050             lappend ret $row
4051         }
4052         set prev $child
4053         set prevrow $row
4054     }
4055     return $ret
4058 proc drawlineseg {id row endrow arrowlow} {
4059     global rowidlist displayorder iddrawn linesegs
4060     global canv colormap linespc curview maxlinelen parentlist
4062     set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4063     set le [expr {$row + 1}]
4064     set arrowhigh 1
4065     while {1} {
4066         set c [lsearch -exact [lindex $rowidlist $le] $id]
4067         if {$c < 0} {
4068             incr le -1
4069             break
4070         }
4071         lappend cols $c
4072         set x [lindex $displayorder $le]
4073         if {$x eq $id} {
4074             set arrowhigh 0
4075             break
4076         }
4077         if {[info exists iddrawn($x)] || $le == $endrow} {
4078             set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
4079             if {$c >= 0} {
4080                 lappend cols $c
4081                 set arrowhigh 0
4082             }
4083             break
4084         }
4085         incr le
4086     }
4087     if {$le <= $row} {
4088         return $row
4089     }
4091     set lines {}
4092     set i 0
4093     set joinhigh 0
4094     if {[info exists linesegs($id)]} {
4095         set lines $linesegs($id)
4096         foreach li $lines {
4097             set r0 [lindex $li 0]
4098             if {$r0 > $row} {
4099                 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
4100                     set joinhigh 1
4101                 }
4102                 break
4103             }
4104             incr i
4105         }
4106     }
4107     set joinlow 0
4108     if {$i > 0} {
4109         set li [lindex $lines [expr {$i-1}]]
4110         set r1 [lindex $li 1]
4111         if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
4112             set joinlow 1
4113         }
4114     }
4116     set x [lindex $cols [expr {$le - $row}]]
4117     set xp [lindex $cols [expr {$le - 1 - $row}]]
4118     set dir [expr {$xp - $x}]
4119     if {$joinhigh} {
4120         set ith [lindex $lines $i 2]
4121         set coords [$canv coords $ith]
4122         set ah [$canv itemcget $ith -arrow]
4123         set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
4124         set x2 [lindex $cols [expr {$le + 1 - $row}]]
4125         if {$x2 ne {} && $x - $x2 == $dir} {
4126             set coords [lrange $coords 0 end-2]
4127         }
4128     } else {
4129         set coords [list [xc $le $x] [yc $le]]
4130     }
4131     if {$joinlow} {
4132         set itl [lindex $lines [expr {$i-1}] 2]
4133         set al [$canv itemcget $itl -arrow]
4134         set arrowlow [expr {$al eq "last" || $al eq "both"}]
4135     } elseif {$arrowlow} {
4136         if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
4137             [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
4138             set arrowlow 0
4139         }
4140     }
4141     set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
4142     for {set y $le} {[incr y -1] > $row} {} {
4143         set x $xp
4144         set xp [lindex $cols [expr {$y - 1 - $row}]]
4145         set ndir [expr {$xp - $x}]
4146         if {$dir != $ndir || $xp < 0} {
4147             lappend coords [xc $y $x] [yc $y]
4148         }
4149         set dir $ndir
4150     }
4151     if {!$joinlow} {
4152         if {$xp < 0} {
4153             # join parent line to first child
4154             set ch [lindex $displayorder $row]
4155             set xc [lsearch -exact [lindex $rowidlist $row] $ch]
4156             if {$xc < 0} {
4157                 puts "oops: drawlineseg: child $ch not on row $row"
4158             } elseif {$xc != $x} {
4159                 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
4160                     set d [expr {int(0.5 * $linespc)}]
4161                     set x1 [xc $row $x]
4162                     if {$xc < $x} {
4163                         set x2 [expr {$x1 - $d}]
4164                     } else {
4165                         set x2 [expr {$x1 + $d}]
4166                     }
4167                     set y2 [yc $row]
4168                     set y1 [expr {$y2 + $d}]
4169                     lappend coords $x1 $y1 $x2 $y2
4170                 } elseif {$xc < $x - 1} {
4171                     lappend coords [xc $row [expr {$x-1}]] [yc $row]
4172                 } elseif {$xc > $x + 1} {
4173                     lappend coords [xc $row [expr {$x+1}]] [yc $row]
4174                 }
4175                 set x $xc
4176             }
4177             lappend coords [xc $row $x] [yc $row]
4178         } else {
4179             set xn [xc $row $xp]
4180             set yn [yc $row]
4181             lappend coords $xn $yn
4182         }
4183         if {!$joinhigh} {
4184             assigncolor $id
4185             set t [$canv create line $coords -width [linewidth $id] \
4186                        -fill $colormap($id) -tags lines.$id -arrow $arrow]
4187             $canv lower $t
4188             bindline $t $id
4189             set lines [linsert $lines $i [list $row $le $t]]
4190         } else {
4191             $canv coords $ith $coords
4192             if {$arrow ne $ah} {
4193                 $canv itemconf $ith -arrow $arrow
4194             }
4195             lset lines $i 0 $row
4196         }
4197     } else {
4198         set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
4199         set ndir [expr {$xo - $xp}]
4200         set clow [$canv coords $itl]
4201         if {$dir == $ndir} {
4202             set clow [lrange $clow 2 end]
4203         }
4204         set coords [concat $coords $clow]
4205         if {!$joinhigh} {
4206             lset lines [expr {$i-1}] 1 $le
4207         } else {
4208             # coalesce two pieces
4209             $canv delete $ith
4210             set b [lindex $lines [expr {$i-1}] 0]
4211             set e [lindex $lines $i 1]
4212             set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
4213         }
4214         $canv coords $itl $coords
4215         if {$arrow ne $al} {
4216             $canv itemconf $itl -arrow $arrow
4217         }
4218     }
4220     set linesegs($id) $lines
4221     return $le
4224 proc drawparentlinks {id row} {
4225     global rowidlist canv colormap curview parentlist
4226     global idpos linespc
4228     set rowids [lindex $rowidlist $row]
4229     set col [lsearch -exact $rowids $id]
4230     if {$col < 0} return
4231     set olds [lindex $parentlist $row]
4232     set row2 [expr {$row + 1}]
4233     set x [xc $row $col]
4234     set y [yc $row]
4235     set y2 [yc $row2]
4236     set d [expr {int(0.5 * $linespc)}]
4237     set ymid [expr {$y + $d}]
4238     set ids [lindex $rowidlist $row2]
4239     # rmx = right-most X coord used
4240     set rmx 0
4241     foreach p $olds {
4242         set i [lsearch -exact $ids $p]
4243         if {$i < 0} {
4244             puts "oops, parent $p of $id not in list"
4245             continue
4246         }
4247         set x2 [xc $row2 $i]
4248         if {$x2 > $rmx} {
4249             set rmx $x2
4250         }
4251         set j [lsearch -exact $rowids $p]
4252         if {$j < 0} {
4253             # drawlineseg will do this one for us
4254             continue
4255         }
4256         assigncolor $p
4257         # should handle duplicated parents here...
4258         set coords [list $x $y]
4259         if {$i != $col} {
4260             # if attaching to a vertical segment, draw a smaller
4261             # slant for visual distinctness
4262             if {$i == $j} {
4263                 if {$i < $col} {
4264                     lappend coords [expr {$x2 + $d}] $y $x2 $ymid
4265                 } else {
4266                     lappend coords [expr {$x2 - $d}] $y $x2 $ymid
4267                 }
4268             } elseif {$i < $col && $i < $j} {
4269                 # segment slants towards us already
4270                 lappend coords [xc $row $j] $y
4271             } else {
4272                 if {$i < $col - 1} {
4273                     lappend coords [expr {$x2 + $linespc}] $y
4274                 } elseif {$i > $col + 1} {
4275                     lappend coords [expr {$x2 - $linespc}] $y
4276                 }
4277                 lappend coords $x2 $y2
4278             }
4279         } else {
4280             lappend coords $x2 $y2
4281         }
4282         set t [$canv create line $coords -width [linewidth $p] \
4283                    -fill $colormap($p) -tags lines.$p]
4284         $canv lower $t
4285         bindline $t $p
4286     }
4287     if {$rmx > [lindex $idpos($id) 1]} {
4288         lset idpos($id) 1 $rmx
4289         redrawtags $id
4290     }
4293 proc drawlines {id} {
4294     global canv
4296     $canv itemconf lines.$id -width [linewidth $id]
4299 proc drawcmittext {id row col} {
4300     global linespc canv canv2 canv3 fgcolor curview
4301     global cmitlisted commitinfo rowidlist parentlist
4302     global rowtextx idpos idtags idheads idotherrefs
4303     global linehtag linentag linedtag selectedline
4304     global canvxmax boldrows boldnamerows fgcolor nullid nullid2
4306     # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
4307     set listed $cmitlisted($curview,$id)
4308     if {$id eq $nullid} {
4309         set ofill red
4310     } elseif {$id eq $nullid2} {
4311         set ofill green
4312     } else {
4313         set ofill [expr {$listed != 0? "blue": "white"}]
4314     }
4315     set x [xc $row $col]
4316     set y [yc $row]
4317     set orad [expr {$linespc / 3}]
4318     if {$listed <= 1} {
4319         set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
4320                    [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4321                    -fill $ofill -outline $fgcolor -width 1 -tags circle]
4322     } elseif {$listed == 2} {
4323         # triangle pointing left for left-side commits
4324         set t [$canv create polygon \
4325                    [expr {$x - $orad}] $y \
4326                    [expr {$x + $orad - 1}] [expr {$y - $orad}] \
4327                    [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4328                    -fill $ofill -outline $fgcolor -width 1 -tags circle]
4329     } else {
4330         # triangle pointing right for right-side commits
4331         set t [$canv create polygon \
4332                    [expr {$x + $orad - 1}] $y \
4333                    [expr {$x - $orad}] [expr {$y - $orad}] \
4334                    [expr {$x - $orad}] [expr {$y + $orad - 1}] \
4335                    -fill $ofill -outline $fgcolor -width 1 -tags circle]
4336     }
4337     $canv raise $t
4338     $canv bind $t <1> {selcanvline {} %x %y}
4339     set rmx [llength [lindex $rowidlist $row]]
4340     set olds [lindex $parentlist $row]
4341     if {$olds ne {}} {
4342         set nextids [lindex $rowidlist [expr {$row + 1}]]
4343         foreach p $olds {
4344             set i [lsearch -exact $nextids $p]
4345             if {$i > $rmx} {
4346                 set rmx $i
4347             }
4348         }
4349     }
4350     set xt [xc $row $rmx]
4351     set rowtextx($row) $xt
4352     set idpos($id) [list $x $xt $y]
4353     if {[info exists idtags($id)] || [info exists idheads($id)]
4354         || [info exists idotherrefs($id)]} {
4355         set xt [drawtags $id $x $xt $y]
4356     }
4357     set headline [lindex $commitinfo($id) 0]
4358     set name [lindex $commitinfo($id) 1]
4359     set date [lindex $commitinfo($id) 2]
4360     set date [formatdate $date]
4361     set font mainfont
4362     set nfont mainfont
4363     set isbold [ishighlighted $row]
4364     if {$isbold > 0} {
4365         lappend boldrows $row
4366         set font mainfontbold
4367         if {$isbold > 1} {
4368             lappend boldnamerows $row
4369             set nfont mainfontbold
4370         }
4371     }
4372     set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
4373                             -text $headline -font $font -tags text]
4374     $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
4375     set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
4376                             -text $name -font $nfont -tags text]
4377     set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
4378                             -text $date -font mainfont -tags text]
4379     if {[info exists selectedline] && $selectedline == $row} {
4380         make_secsel $row
4381     }
4382     set xr [expr {$xt + [font measure $font $headline]}]
4383     if {$xr > $canvxmax} {
4384         set canvxmax $xr
4385         setcanvscroll
4386     }
4389 proc drawcmitrow {row} {
4390     global displayorder rowidlist nrows_drawn
4391     global iddrawn markingmatches
4392     global commitinfo numcommits
4393     global filehighlight fhighlights findpattern nhighlights
4394     global hlview vhighlights
4395     global highlight_related rhighlights
4397     if {$row >= $numcommits} return
4399     set id [lindex $displayorder $row]
4400     if {[info exists hlview] && ![info exists vhighlights($row)]} {
4401         askvhighlight $row $id
4402     }
4403     if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
4404         askfilehighlight $row $id
4405     }
4406     if {$findpattern ne {} && ![info exists nhighlights($row)]} {
4407         askfindhighlight $row $id
4408     }
4409     if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
4410         askrelhighlight $row $id
4411     }
4412     if {![info exists iddrawn($id)]} {
4413         set col [lsearch -exact [lindex $rowidlist $row] $id]
4414         if {$col < 0} {
4415             puts "oops, row $row id $id not in list"
4416             return
4417         }
4418         if {![info exists commitinfo($id)]} {
4419             getcommit $id
4420         }
4421         assigncolor $id
4422         drawcmittext $id $row $col
4423         set iddrawn($id) 1
4424         incr nrows_drawn
4425     }
4426     if {$markingmatches} {
4427         markrowmatches $row $id
4428     }
4431 proc drawcommits {row {endrow {}}} {
4432     global numcommits iddrawn displayorder curview need_redisplay
4433     global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
4435     if {$row < 0} {
4436         set row 0
4437     }
4438     if {$endrow eq {}} {
4439         set endrow $row
4440     }
4441     if {$endrow >= $numcommits} {
4442         set endrow [expr {$numcommits - 1}]
4443     }
4445     set rl1 [expr {$row - $downarrowlen - 3}]
4446     if {$rl1 < 0} {
4447         set rl1 0
4448     }
4449     set ro1 [expr {$row - 3}]
4450     if {$ro1 < 0} {
4451         set ro1 0
4452     }
4453     set r2 [expr {$endrow + $uparrowlen + 3}]
4454     if {$r2 > $numcommits} {
4455         set r2 $numcommits
4456     }
4457     for {set r $rl1} {$r < $r2} {incr r} {
4458         if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
4459             if {$rl1 < $r} {
4460                 layoutrows $rl1 $r
4461             }
4462             set rl1 [expr {$r + 1}]
4463         }
4464     }
4465     if {$rl1 < $r} {
4466         layoutrows $rl1 $r
4467     }
4468     optimize_rows $ro1 0 $r2
4469     if {$need_redisplay || $nrows_drawn > 2000} {
4470         clear_display
4471         drawvisible
4472     }
4474     # make the lines join to already-drawn rows either side
4475     set r [expr {$row - 1}]
4476     if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
4477         set r $row
4478     }
4479     set er [expr {$endrow + 1}]
4480     if {$er >= $numcommits ||
4481         ![info exists iddrawn([lindex $displayorder $er])]} {
4482         set er $endrow
4483     }
4484     for {} {$r <= $er} {incr r} {
4485         set id [lindex $displayorder $r]
4486         set wasdrawn [info exists iddrawn($id)]
4487         drawcmitrow $r
4488         if {$r == $er} break
4489         set nextid [lindex $displayorder [expr {$r + 1}]]
4490         if {$wasdrawn && [info exists iddrawn($nextid)]} continue
4491         drawparentlinks $id $r
4493         set rowids [lindex $rowidlist $r]
4494         foreach lid $rowids {
4495             if {$lid eq {}} continue
4496             if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
4497             if {$lid eq $id} {
4498                 # see if this is the first child of any of its parents
4499                 foreach p [lindex $parentlist $r] {
4500                     if {[lsearch -exact $rowids $p] < 0} {
4501                         # make this line extend up to the child
4502                         set lineend($p) [drawlineseg $p $r $er 0]
4503                     }
4504                 }
4505             } else {
4506                 set lineend($lid) [drawlineseg $lid $r $er 1]
4507             }
4508         }
4509     }
4512 proc undolayout {row} {
4513     global uparrowlen mingaplen downarrowlen
4514     global rowidlist rowisopt rowfinal need_redisplay
4516     set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
4517     if {$r < 0} {
4518         set r 0
4519     }
4520     if {[llength $rowidlist] > $r} {
4521         incr r -1
4522         set rowidlist [lrange $rowidlist 0 $r]
4523         set rowfinal [lrange $rowfinal 0 $r]
4524         set rowisopt [lrange $rowisopt 0 $r]
4525         set need_redisplay 1
4526         run drawvisible
4527     }
4530 proc drawfrac {f0 f1} {
4531     global canv linespc
4533     set ymax [lindex [$canv cget -scrollregion] 3]
4534     if {$ymax eq {} || $ymax == 0} return
4535     set y0 [expr {int($f0 * $ymax)}]
4536     set row [expr {int(($y0 - 3) / $linespc) - 1}]
4537     set y1 [expr {int($f1 * $ymax)}]
4538     set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
4539     drawcommits $row $endrow
4542 proc drawvisible {} {
4543     global canv
4544     eval drawfrac [$canv yview]
4547 proc clear_display {} {
4548     global iddrawn linesegs need_redisplay nrows_drawn
4549     global vhighlights fhighlights nhighlights rhighlights
4551     allcanvs delete all
4552     catch {unset iddrawn}
4553     catch {unset linesegs}
4554     catch {unset vhighlights}
4555     catch {unset fhighlights}
4556     catch {unset nhighlights}
4557     catch {unset rhighlights}
4558     set need_redisplay 0
4559     set nrows_drawn 0
4562 proc findcrossings {id} {
4563     global rowidlist parentlist numcommits displayorder
4565     set cross {}
4566     set ccross {}
4567     foreach {s e} [rowranges $id] {
4568         if {$e >= $numcommits} {
4569             set e [expr {$numcommits - 1}]
4570         }
4571         if {$e <= $s} continue
4572         for {set row $e} {[incr row -1] >= $s} {} {
4573             set x [lsearch -exact [lindex $rowidlist $row] $id]
4574             if {$x < 0} break
4575             set olds [lindex $parentlist $row]
4576             set kid [lindex $displayorder $row]
4577             set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
4578             if {$kidx < 0} continue
4579             set nextrow [lindex $rowidlist [expr {$row + 1}]]
4580             foreach p $olds {
4581                 set px [lsearch -exact $nextrow $p]
4582                 if {$px < 0} continue
4583                 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
4584                     if {[lsearch -exact $ccross $p] >= 0} continue
4585                     if {$x == $px + ($kidx < $px? -1: 1)} {
4586                         lappend ccross $p
4587                     } elseif {[lsearch -exact $cross $p] < 0} {
4588                         lappend cross $p
4589                     }
4590                 }
4591             }
4592         }
4593     }
4594     return [concat $ccross {{}} $cross]
4597 proc assigncolor {id} {
4598     global colormap colors nextcolor
4599     global parents children children curview
4601     if {[info exists colormap($id)]} return
4602     set ncolors [llength $colors]
4603     if {[info exists children($curview,$id)]} {
4604         set kids $children($curview,$id)
4605     } else {
4606         set kids {}
4607     }
4608     if {[llength $kids] == 1} {
4609         set child [lindex $kids 0]
4610         if {[info exists colormap($child)]
4611             && [llength $parents($curview,$child)] == 1} {
4612             set colormap($id) $colormap($child)
4613             return
4614         }
4615     }
4616     set badcolors {}
4617     set origbad {}
4618     foreach x [findcrossings $id] {
4619         if {$x eq {}} {
4620             # delimiter between corner crossings and other crossings
4621             if {[llength $badcolors] >= $ncolors - 1} break
4622             set origbad $badcolors
4623         }
4624         if {[info exists colormap($x)]
4625             && [lsearch -exact $badcolors $colormap($x)] < 0} {
4626             lappend badcolors $colormap($x)
4627         }
4628     }
4629     if {[llength $badcolors] >= $ncolors} {
4630         set badcolors $origbad
4631     }
4632     set origbad $badcolors
4633     if {[llength $badcolors] < $ncolors - 1} {
4634         foreach child $kids {
4635             if {[info exists colormap($child)]
4636                 && [lsearch -exact $badcolors $colormap($child)] < 0} {
4637                 lappend badcolors $colormap($child)
4638             }
4639             foreach p $parents($curview,$child) {
4640                 if {[info exists colormap($p)]
4641                     && [lsearch -exact $badcolors $colormap($p)] < 0} {
4642                     lappend badcolors $colormap($p)
4643                 }
4644             }
4645         }
4646         if {[llength $badcolors] >= $ncolors} {
4647             set badcolors $origbad
4648         }
4649     }
4650     for {set i 0} {$i <= $ncolors} {incr i} {
4651         set c [lindex $colors $nextcolor]
4652         if {[incr nextcolor] >= $ncolors} {
4653             set nextcolor 0
4654         }
4655         if {[lsearch -exact $badcolors $c]} break
4656     }
4657     set colormap($id) $c
4660 proc bindline {t id} {
4661     global canv
4663     $canv bind $t <Enter> "lineenter %x %y $id"
4664     $canv bind $t <Motion> "linemotion %x %y $id"
4665     $canv bind $t <Leave> "lineleave $id"
4666     $canv bind $t <Button-1> "lineclick %x %y $id 1"
4669 proc drawtags {id x xt y1} {
4670     global idtags idheads idotherrefs mainhead
4671     global linespc lthickness
4672     global canv rowtextx curview fgcolor bgcolor
4674     set marks {}
4675     set ntags 0
4676     set nheads 0
4677     if {[info exists idtags($id)]} {
4678         set marks $idtags($id)
4679         set ntags [llength $marks]
4680     }
4681     if {[info exists idheads($id)]} {
4682         set marks [concat $marks $idheads($id)]
4683         set nheads [llength $idheads($id)]
4684     }
4685     if {[info exists idotherrefs($id)]} {
4686         set marks [concat $marks $idotherrefs($id)]
4687     }
4688     if {$marks eq {}} {
4689         return $xt
4690     }
4692     set delta [expr {int(0.5 * ($linespc - $lthickness))}]
4693     set yt [expr {$y1 - 0.5 * $linespc}]
4694     set yb [expr {$yt + $linespc - 1}]
4695     set xvals {}
4696     set wvals {}
4697     set i -1
4698     foreach tag $marks {
4699         incr i
4700         if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
4701             set wid [font measure mainfontbold $tag]
4702         } else {
4703             set wid [font measure mainfont $tag]
4704         }
4705         lappend xvals $xt
4706         lappend wvals $wid
4707         set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
4708     }
4709     set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
4710                -width $lthickness -fill black -tags tag.$id]
4711     $canv lower $t
4712     foreach tag $marks x $xvals wid $wvals {
4713         set xl [expr {$x + $delta}]
4714         set xr [expr {$x + $delta + $wid + $lthickness}]
4715         set font mainfont
4716         if {[incr ntags -1] >= 0} {
4717             # draw a tag
4718             set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
4719                        $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
4720                        -width 1 -outline black -fill yellow -tags tag.$id]
4721             $canv bind $t <1> [list showtag $tag 1]
4722             set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
4723         } else {
4724             # draw a head or other ref
4725             if {[incr nheads -1] >= 0} {
4726                 set col green
4727                 if {$tag eq $mainhead} {
4728                     set font mainfontbold
4729                 }
4730             } else {
4731                 set col "#ddddff"
4732             }
4733             set xl [expr {$xl - $delta/2}]
4734             $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
4735                 -width 1 -outline black -fill $col -tags tag.$id
4736             if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
4737                 set rwid [font measure mainfont $remoteprefix]
4738                 set xi [expr {$x + 1}]
4739                 set yti [expr {$yt + 1}]
4740                 set xri [expr {$x + $rwid}]
4741                 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
4742                         -width 0 -fill "#ffddaa" -tags tag.$id
4743             }
4744         }
4745         set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
4746                    -font $font -tags [list tag.$id text]]
4747         if {$ntags >= 0} {
4748             $canv bind $t <1> [list showtag $tag 1]
4749         } elseif {$nheads >= 0} {
4750             $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
4751         }
4752     }
4753     return $xt
4756 proc xcoord {i level ln} {
4757     global canvx0 xspc1 xspc2
4759     set x [expr {$canvx0 + $i * $xspc1($ln)}]
4760     if {$i > 0 && $i == $level} {
4761         set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
4762     } elseif {$i > $level} {
4763         set x [expr {$x + $xspc2 - $xspc1($ln)}]
4764     }
4765     return $x
4768 proc show_status {msg} {
4769     global canv fgcolor
4771     clear_display
4772     $canv create text 3 3 -anchor nw -text $msg -font mainfont \
4773         -tags text -fill $fgcolor
4776 # Don't change the text pane cursor if it is currently the hand cursor,
4777 # showing that we are over a sha1 ID link.
4778 proc settextcursor {c} {
4779     global ctext curtextcursor
4781     if {[$ctext cget -cursor] == $curtextcursor} {
4782         $ctext config -cursor $c
4783     }
4784     set curtextcursor $c
4787 proc nowbusy {what {name {}}} {
4788     global isbusy busyname statusw
4790     if {[array names isbusy] eq {}} {
4791         . config -cursor watch
4792         settextcursor watch
4793     }
4794     set isbusy($what) 1
4795     set busyname($what) $name
4796     if {$name ne {}} {
4797         $statusw conf -text $name
4798     }
4801 proc notbusy {what} {
4802     global isbusy maincursor textcursor busyname statusw
4804     catch {
4805         unset isbusy($what)
4806         if {$busyname($what) ne {} &&
4807             [$statusw cget -text] eq $busyname($what)} {
4808             $statusw conf -text {}
4809         }
4810     }
4811     if {[array names isbusy] eq {}} {
4812         . config -cursor $maincursor
4813         settextcursor $textcursor
4814     }
4817 proc findmatches {f} {
4818     global findtype findstring
4819     if {$findtype == "Regexp"} {
4820         set matches [regexp -indices -all -inline $findstring $f]
4821     } else {
4822         set fs $findstring
4823         if {$findtype == "IgnCase"} {
4824             set f [string tolower $f]
4825             set fs [string tolower $fs]
4826         }
4827         set matches {}
4828         set i 0
4829         set l [string length $fs]
4830         while {[set j [string first $fs $f $i]] >= 0} {
4831             lappend matches [list $j [expr {$j+$l-1}]]
4832             set i [expr {$j + $l}]
4833         }
4834     }
4835     return $matches
4838 proc dofind {{dirn 1} {wrap 1}} {
4839     global findstring findstartline findcurline selectedline numcommits
4840     global gdttype filehighlight fh_serial find_dirn findallowwrap
4842     if {[info exists find_dirn]} {
4843         if {$find_dirn == $dirn} return
4844         stopfinding
4845     }
4846     focus .
4847     if {$findstring eq {} || $numcommits == 0} return
4848     if {![info exists selectedline]} {
4849         set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
4850     } else {
4851         set findstartline $selectedline
4852     }
4853     set findcurline $findstartline
4854     nowbusy finding "Searching"
4855     if {$gdttype ne "containing:" && ![info exists filehighlight]} {
4856         after cancel do_file_hl $fh_serial
4857         do_file_hl $fh_serial
4858     }
4859     set find_dirn $dirn
4860     set findallowwrap $wrap
4861     run findmore
4864 proc stopfinding {} {
4865     global find_dirn findcurline fprogcoord
4867     if {[info exists find_dirn]} {
4868         unset find_dirn
4869         unset findcurline
4870         notbusy finding
4871         set fprogcoord 0
4872         adjustprogress
4873     }
4876 proc findmore {} {
4877     global commitdata commitinfo numcommits findpattern findloc
4878     global findstartline findcurline findallowwrap
4879     global find_dirn gdttype fhighlights fprogcoord
4880     global curview varcorder vrownum varccommits
4882     if {![info exists find_dirn]} {
4883         return 0
4884     }
4885     set fldtypes {Headline Author Date Committer CDate Comments}
4886     set l $findcurline
4887     set moretodo 0
4888     if {$find_dirn > 0} {
4889         incr l
4890         if {$l >= $numcommits} {
4891             set l 0
4892         }
4893         if {$l <= $findstartline} {
4894             set lim [expr {$findstartline + 1}]
4895         } else {
4896             set lim $numcommits
4897             set moretodo $findallowwrap
4898         }
4899     } else {
4900         if {$l == 0} {
4901             set l $numcommits
4902         }
4903         incr l -1
4904         if {$l >= $findstartline} {
4905             set lim [expr {$findstartline - 1}]
4906         } else {
4907             set lim -1
4908             set moretodo $findallowwrap
4909         }
4910     }
4911     set n [expr {($lim - $l) * $find_dirn}]
4912     if {$n > 500} {
4913         set n 500
4914         set moretodo 1
4915     }
4916     set found 0
4917     set domore 1
4918     set ai [bsearch $vrownum($curview) $l]
4919     set a [lindex $varcorder($curview) $ai]
4920     set arow [lindex $vrownum($curview) $ai]
4921     set ids [lindex $varccommits($curview,$a)]
4922     set arowend [expr {$arow + [llength $ids]}]
4923     if {$gdttype eq "containing:"} {
4924         for {} {$n > 0} {incr n -1; incr l $find_dirn} {
4925             if {$l < $arow || $l >= $arowend} {
4926                 incr ai $find_dirn
4927                 set a [lindex $varcorder($curview) $ai]
4928                 set arow [lindex $vrownum($curview) $ai]
4929                 set ids [lindex $varccommits($curview,$a)]
4930                 set arowend [expr {$arow + [llength $ids]}]
4931             }
4932             set id [lindex $ids [expr {$l - $arow}]]
4933             # shouldn't happen unless git log doesn't give all the commits...
4934             if {![info exists commitdata($id)] ||
4935                 ![doesmatch $commitdata($id)]} {
4936                 continue
4937             }
4938             if {![info exists commitinfo($id)]} {
4939                 getcommit $id
4940             }
4941             set info $commitinfo($id)
4942             foreach f $info ty $fldtypes {
4943                 if {($findloc eq "All fields" || $findloc eq $ty) &&
4944                     [doesmatch $f]} {
4945                     set found 1
4946                     break
4947                 }
4948             }
4949             if {$found} break
4950         }
4951     } else {
4952         for {} {$n > 0} {incr n -1; incr l $find_dirn} {
4953             if {$l < $arow || $l >= $arowend} {
4954                 incr ai $find_dirn
4955                 set a [lindex $varcorder($curview) $ai]
4956                 set arow [lindex $vrownum($curview) $ai]
4957                 set ids [lindex $varccommits($curview,$a)]
4958                 set arowend [expr {$arow + [llength $ids]}]
4959             }
4960             set id [lindex $ids [expr {$l - $arow}]]
4961             if {![info exists fhighlights($l)]} {
4962                 askfilehighlight $l $id
4963                 if {$domore} {
4964                     set domore 0
4965                     set findcurline [expr {$l - $find_dirn}]
4966                 }
4967             } elseif {$fhighlights($l)} {
4968                 set found $domore
4969                 break
4970             }
4971         }
4972     }
4973     if {$found || ($domore && !$moretodo)} {
4974         unset findcurline
4975         unset find_dirn
4976         notbusy finding
4977         set fprogcoord 0
4978         adjustprogress
4979         if {$found} {
4980             findselectline $l
4981         } else {
4982             bell
4983         }
4984         return 0
4985     }
4986     if {!$domore} {
4987         flushhighlights
4988     } else {
4989         set findcurline [expr {$l - $find_dirn}]
4990     }
4991     set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
4992     if {$n < 0} {
4993         incr n $numcommits
4994     }
4995     set fprogcoord [expr {$n * 1.0 / $numcommits}]
4996     adjustprogress
4997     return $domore
5000 proc findselectline {l} {
5001     global findloc commentend ctext findcurline markingmatches gdttype
5003     set markingmatches 1
5004     set findcurline $l
5005     selectline $l 1
5006     if {$findloc == "All fields" || $findloc == "Comments"} {
5007         # highlight the matches in the comments
5008         set f [$ctext get 1.0 $commentend]
5009         set matches [findmatches $f]
5010         foreach match $matches {
5011             set start [lindex $match 0]
5012             set end [expr {[lindex $match 1] + 1}]
5013             $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5014         }
5015     }
5016     drawvisible
5019 # mark the bits of a headline or author that match a find string
5020 proc markmatches {canv l str tag matches font row} {
5021     global selectedline
5023     set bbox [$canv bbox $tag]
5024     set x0 [lindex $bbox 0]
5025     set y0 [lindex $bbox 1]
5026     set y1 [lindex $bbox 3]
5027     foreach match $matches {
5028         set start [lindex $match 0]
5029         set end [lindex $match 1]
5030         if {$start > $end} continue
5031         set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
5032         set xlen [font measure $font [string range $str 0 [expr {$end}]]]
5033         set t [$canv create rect [expr {$x0+$xoff}] $y0 \
5034                    [expr {$x0+$xlen+2}] $y1 \
5035                    -outline {} -tags [list match$l matches] -fill yellow]
5036         $canv lower $t
5037         if {[info exists selectedline] && $row == $selectedline} {
5038             $canv raise $t secsel
5039         }
5040     }
5043 proc unmarkmatches {} {
5044     global markingmatches
5046     allcanvs delete matches
5047     set markingmatches 0
5048     stopfinding
5051 proc selcanvline {w x y} {
5052     global canv canvy0 ctext linespc
5053     global rowtextx
5054     set ymax [lindex [$canv cget -scrollregion] 3]
5055     if {$ymax == {}} return
5056     set yfrac [lindex [$canv yview] 0]
5057     set y [expr {$y + $yfrac * $ymax}]
5058     set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
5059     if {$l < 0} {
5060         set l 0
5061     }
5062     if {$w eq $canv} {
5063         if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
5064     }
5065     unmarkmatches
5066     selectline $l 1
5069 proc commit_descriptor {p} {
5070     global commitinfo
5071     if {![info exists commitinfo($p)]} {
5072         getcommit $p
5073     }
5074     set l "..."
5075     if {[llength $commitinfo($p)] > 1} {
5076         set l [lindex $commitinfo($p) 0]
5077     }
5078     return "$p ($l)\n"
5081 # append some text to the ctext widget, and make any SHA1 ID
5082 # that we know about be a clickable link.
5083 proc appendwithlinks {text tags} {
5084     global ctext linknum curview pendinglinks
5086     set start [$ctext index "end - 1c"]
5087     $ctext insert end $text $tags
5088     set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
5089     foreach l $links {
5090         set s [lindex $l 0]
5091         set e [lindex $l 1]
5092         set linkid [string range $text $s $e]
5093         incr e
5094         $ctext tag delete link$linknum
5095         $ctext tag add link$linknum "$start + $s c" "$start + $e c"
5096         setlink $linkid link$linknum
5097         incr linknum
5098     }
5101 proc setlink {id lk} {
5102     global curview ctext pendinglinks commitinterest
5104     if {[commitinview $id $curview]} {
5105         $ctext tag conf $lk -foreground blue -underline 1
5106         $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1]
5107         $ctext tag bind $lk <Enter> {linkcursor %W 1}
5108         $ctext tag bind $lk <Leave> {linkcursor %W -1}
5109     } else {
5110         lappend pendinglinks($id) $lk
5111         lappend commitinterest($id) {makelink %I}
5112     }
5115 proc makelink {id} {
5116     global pendinglinks
5118     if {![info exists pendinglinks($id)]} return
5119     foreach lk $pendinglinks($id) {
5120         setlink $id $lk
5121     }
5122     unset pendinglinks($id)
5125 proc linkcursor {w inc} {
5126     global linkentercount curtextcursor
5128     if {[incr linkentercount $inc] > 0} {
5129         $w configure -cursor hand2
5130     } else {
5131         $w configure -cursor $curtextcursor
5132         if {$linkentercount < 0} {
5133             set linkentercount 0
5134         }
5135     }
5138 proc viewnextline {dir} {
5139     global canv linespc
5141     $canv delete hover
5142     set ymax [lindex [$canv cget -scrollregion] 3]
5143     set wnow [$canv yview]
5144     set wtop [expr {[lindex $wnow 0] * $ymax}]
5145     set newtop [expr {$wtop + $dir * $linespc}]
5146     if {$newtop < 0} {
5147         set newtop 0
5148     } elseif {$newtop > $ymax} {
5149         set newtop $ymax
5150     }
5151     allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5154 # add a list of tag or branch names at position pos
5155 # returns the number of names inserted
5156 proc appendrefs {pos ids var} {
5157     global ctext linknum curview $var maxrefs
5159     if {[catch {$ctext index $pos}]} {
5160         return 0
5161     }
5162     $ctext conf -state normal
5163     $ctext delete $pos "$pos lineend"
5164     set tags {}
5165     foreach id $ids {
5166         foreach tag [set $var\($id\)] {
5167             lappend tags [list $tag $id]
5168         }
5169     }
5170     if {[llength $tags] > $maxrefs} {
5171         $ctext insert $pos "many ([llength $tags])"
5172     } else {
5173         set tags [lsort -index 0 -decreasing $tags]
5174         set sep {}
5175         foreach ti $tags {
5176             set id [lindex $ti 1]
5177             set lk link$linknum
5178             incr linknum
5179             $ctext tag delete $lk
5180             $ctext insert $pos $sep
5181             $ctext insert $pos [lindex $ti 0] $lk
5182             setlink $id $lk
5183             set sep ", "
5184         }
5185     }
5186     $ctext conf -state disabled
5187     return [llength $tags]
5190 # called when we have finished computing the nearby tags
5191 proc dispneartags {delay} {
5192     global selectedline currentid showneartags tagphase
5194     if {![info exists selectedline] || !$showneartags} return
5195     after cancel dispnexttag
5196     if {$delay} {
5197         after 200 dispnexttag
5198         set tagphase -1
5199     } else {
5200         after idle dispnexttag
5201         set tagphase 0
5202     }
5205 proc dispnexttag {} {
5206     global selectedline currentid showneartags tagphase ctext
5208     if {![info exists selectedline] || !$showneartags} return
5209     switch -- $tagphase {
5210         0 {
5211             set dtags [desctags $currentid]
5212             if {$dtags ne {}} {
5213                 appendrefs precedes $dtags idtags
5214             }
5215         }
5216         1 {
5217             set atags [anctags $currentid]
5218             if {$atags ne {}} {
5219                 appendrefs follows $atags idtags
5220             }
5221         }
5222         2 {
5223             set dheads [descheads $currentid]
5224             if {$dheads ne {}} {
5225                 if {[appendrefs branch $dheads idheads] > 1
5226                     && [$ctext get "branch -3c"] eq "h"} {
5227                     # turn "Branch" into "Branches"
5228                     $ctext conf -state normal
5229                     $ctext insert "branch -2c" "es"
5230                     $ctext conf -state disabled
5231                 }
5232             }
5233         }
5234     }
5235     if {[incr tagphase] <= 2} {
5236         after idle dispnexttag
5237     }
5240 proc make_secsel {l} {
5241     global linehtag linentag linedtag canv canv2 canv3
5243     if {![info exists linehtag($l)]} return
5244     $canv delete secsel
5245     set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
5246                -tags secsel -fill [$canv cget -selectbackground]]
5247     $canv lower $t
5248     $canv2 delete secsel
5249     set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
5250                -tags secsel -fill [$canv2 cget -selectbackground]]
5251     $canv2 lower $t
5252     $canv3 delete secsel
5253     set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
5254                -tags secsel -fill [$canv3 cget -selectbackground]]
5255     $canv3 lower $t
5258 proc selectline {l isnew} {
5259     global canv ctext commitinfo selectedline
5260     global canvy0 linespc parents children curview
5261     global currentid sha1entry
5262     global commentend idtags linknum
5263     global mergemax numcommits pending_select
5264     global cmitmode showneartags allcommits
5266     catch {unset pending_select}
5267     $canv delete hover
5268     normalline
5269     unsel_reflist
5270     stopfinding
5271     if {$l < 0 || $l >= $numcommits} return
5272     set y [expr {$canvy0 + $l * $linespc}]
5273     set ymax [lindex [$canv cget -scrollregion] 3]
5274     set ytop [expr {$y - $linespc - 1}]
5275     set ybot [expr {$y + $linespc + 1}]
5276     set wnow [$canv yview]
5277     set wtop [expr {[lindex $wnow 0] * $ymax}]
5278     set wbot [expr {[lindex $wnow 1] * $ymax}]
5279     set wh [expr {$wbot - $wtop}]
5280     set newtop $wtop
5281     if {$ytop < $wtop} {
5282         if {$ybot < $wtop} {
5283             set newtop [expr {$y - $wh / 2.0}]
5284         } else {
5285             set newtop $ytop
5286             if {$newtop > $wtop - $linespc} {
5287                 set newtop [expr {$wtop - $linespc}]
5288             }
5289         }
5290     } elseif {$ybot > $wbot} {
5291         if {$ytop > $wbot} {
5292             set newtop [expr {$y - $wh / 2.0}]
5293         } else {
5294             set newtop [expr {$ybot - $wh}]
5295             if {$newtop < $wtop + $linespc} {
5296                 set newtop [expr {$wtop + $linespc}]
5297             }
5298         }
5299     }
5300     if {$newtop != $wtop} {
5301         if {$newtop < 0} {
5302             set newtop 0
5303         }
5304         allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5305         drawvisible
5306     }
5308     make_secsel $l
5310     if {$isnew} {
5311         addtohistory [list selectline $l 0]
5312     }
5314     set selectedline $l
5316     set id [commitonrow $l]
5317     set currentid $id
5318     $sha1entry delete 0 end
5319     $sha1entry insert 0 $id
5320     $sha1entry selection from 0
5321     $sha1entry selection to end
5322     rhighlight_sel $id
5324     $ctext conf -state normal
5325     clear_ctext
5326     set linknum 0
5327     set info $commitinfo($id)
5328     set date [formatdate [lindex $info 2]]
5329     $ctext insert end "Author: [lindex $info 1]  $date\n"
5330     set date [formatdate [lindex $info 4]]
5331     $ctext insert end "Committer: [lindex $info 3]  $date\n"
5332     if {[info exists idtags($id)]} {
5333         $ctext insert end "Tags:"
5334         foreach tag $idtags($id) {
5335             $ctext insert end " $tag"
5336         }
5337         $ctext insert end "\n"
5338     }
5340     set headers {}
5341     set olds $parents($curview,$id)
5342     if {[llength $olds] > 1} {
5343         set np 0
5344         foreach p $olds {
5345             if {$np >= $mergemax} {
5346                 set tag mmax
5347             } else {
5348                 set tag m$np
5349             }
5350             $ctext insert end "Parent: " $tag
5351             appendwithlinks [commit_descriptor $p] {}
5352             incr np
5353         }
5354     } else {
5355         foreach p $olds {
5356             append headers "Parent: [commit_descriptor $p]"
5357         }
5358     }
5360     foreach c $children($curview,$id) {
5361         append headers "Child:  [commit_descriptor $c]"
5362     }
5364     # make anything that looks like a SHA1 ID be a clickable link
5365     appendwithlinks $headers {}
5366     if {$showneartags} {
5367         if {![info exists allcommits]} {
5368             getallcommits
5369         }
5370         $ctext insert end "Branch: "
5371         $ctext mark set branch "end -1c"
5372         $ctext mark gravity branch left
5373         $ctext insert end "\nFollows: "
5374         $ctext mark set follows "end -1c"
5375         $ctext mark gravity follows left
5376         $ctext insert end "\nPrecedes: "
5377         $ctext mark set precedes "end -1c"
5378         $ctext mark gravity precedes left
5379         $ctext insert end "\n"
5380         dispneartags 1
5381     }
5382     $ctext insert end "\n"
5383     set comment [lindex $info 5]
5384     if {[string first "\r" $comment] >= 0} {
5385         set comment [string map {"\r" "\n    "} $comment]
5386     }
5387     appendwithlinks $comment {comment}
5389     $ctext tag remove found 1.0 end
5390     $ctext conf -state disabled
5391     set commentend [$ctext index "end - 1c"]
5393     init_flist "Comments"
5394     if {$cmitmode eq "tree"} {
5395         gettree $id
5396     } elseif {[llength $olds] <= 1} {
5397         startdiff $id
5398     } else {
5399         mergediff $id
5400     }
5403 proc selfirstline {} {
5404     unmarkmatches
5405     selectline 0 1
5408 proc sellastline {} {
5409     global numcommits
5410     unmarkmatches
5411     set l [expr {$numcommits - 1}]
5412     selectline $l 1
5415 proc selnextline {dir} {
5416     global selectedline
5417     focus .
5418     if {![info exists selectedline]} return
5419     set l [expr {$selectedline + $dir}]
5420     unmarkmatches
5421     selectline $l 1
5424 proc selnextpage {dir} {
5425     global canv linespc selectedline numcommits
5427     set lpp [expr {([winfo height $canv] - 2) / $linespc}]
5428     if {$lpp < 1} {
5429         set lpp 1
5430     }
5431     allcanvs yview scroll [expr {$dir * $lpp}] units
5432     drawvisible
5433     if {![info exists selectedline]} return
5434     set l [expr {$selectedline + $dir * $lpp}]
5435     if {$l < 0} {
5436         set l 0
5437     } elseif {$l >= $numcommits} {
5438         set l [expr $numcommits - 1]
5439     }
5440     unmarkmatches
5441     selectline $l 1
5444 proc unselectline {} {
5445     global selectedline currentid
5447     catch {unset selectedline}
5448     catch {unset currentid}
5449     allcanvs delete secsel
5450     rhighlight_none
5453 proc reselectline {} {
5454     global selectedline
5456     if {[info exists selectedline]} {
5457         selectline $selectedline 0
5458     }
5461 proc addtohistory {cmd} {
5462     global history historyindex curview
5464     set elt [list $curview $cmd]
5465     if {$historyindex > 0
5466         && [lindex $history [expr {$historyindex - 1}]] == $elt} {
5467         return
5468     }
5470     if {$historyindex < [llength $history]} {
5471         set history [lreplace $history $historyindex end $elt]
5472     } else {
5473         lappend history $elt
5474     }
5475     incr historyindex
5476     if {$historyindex > 1} {
5477         .tf.bar.leftbut conf -state normal
5478     } else {
5479         .tf.bar.leftbut conf -state disabled
5480     }
5481     .tf.bar.rightbut conf -state disabled
5484 proc godo {elt} {
5485     global curview
5487     set view [lindex $elt 0]
5488     set cmd [lindex $elt 1]
5489     if {$curview != $view} {
5490         showview $view
5491     }
5492     eval $cmd
5495 proc goback {} {
5496     global history historyindex
5497     focus .
5499     if {$historyindex > 1} {
5500         incr historyindex -1
5501         godo [lindex $history [expr {$historyindex - 1}]]
5502         .tf.bar.rightbut conf -state normal
5503     }
5504     if {$historyindex <= 1} {
5505         .tf.bar.leftbut conf -state disabled
5506     }
5509 proc goforw {} {
5510     global history historyindex
5511     focus .
5513     if {$historyindex < [llength $history]} {
5514         set cmd [lindex $history $historyindex]
5515         incr historyindex
5516         godo $cmd
5517         .tf.bar.leftbut conf -state normal
5518     }
5519     if {$historyindex >= [llength $history]} {
5520         .tf.bar.rightbut conf -state disabled
5521     }
5524 proc gettree {id} {
5525     global treefilelist treeidlist diffids diffmergeid treepending
5526     global nullid nullid2
5528     set diffids $id
5529     catch {unset diffmergeid}
5530     if {![info exists treefilelist($id)]} {
5531         if {![info exists treepending]} {
5532             if {$id eq $nullid} {
5533                 set cmd [list | git ls-files]
5534             } elseif {$id eq $nullid2} {
5535                 set cmd [list | git ls-files --stage -t]
5536             } else {
5537                 set cmd [list | git ls-tree -r $id]
5538             }
5539             if {[catch {set gtf [open $cmd r]}]} {
5540                 return
5541             }
5542             set treepending $id
5543             set treefilelist($id) {}
5544             set treeidlist($id) {}
5545             fconfigure $gtf -blocking 0
5546             filerun $gtf [list gettreeline $gtf $id]
5547         }
5548     } else {
5549         setfilelist $id
5550     }
5553 proc gettreeline {gtf id} {
5554     global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
5556     set nl 0
5557     while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
5558         if {$diffids eq $nullid} {
5559             set fname $line
5560         } else {
5561             if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
5562             set i [string first "\t" $line]
5563             if {$i < 0} continue
5564             set sha1 [lindex $line 2]
5565             set fname [string range $line [expr {$i+1}] end]
5566             if {[string index $fname 0] eq "\""} {
5567                 set fname [lindex $fname 0]
5568             }
5569             lappend treeidlist($id) $sha1
5570         }
5571         lappend treefilelist($id) $fname
5572     }
5573     if {![eof $gtf]} {
5574         return [expr {$nl >= 1000? 2: 1}]
5575     }
5576     close $gtf
5577     unset treepending
5578     if {$cmitmode ne "tree"} {
5579         if {![info exists diffmergeid]} {
5580             gettreediffs $diffids
5581         }
5582     } elseif {$id ne $diffids} {
5583         gettree $diffids
5584     } else {
5585         setfilelist $id
5586     }
5587     return 0
5590 proc showfile {f} {
5591     global treefilelist treeidlist diffids nullid nullid2
5592     global ctext commentend
5594     set i [lsearch -exact $treefilelist($diffids) $f]
5595     if {$i < 0} {
5596         puts "oops, $f not in list for id $diffids"
5597         return
5598     }
5599     if {$diffids eq $nullid} {
5600         if {[catch {set bf [open $f r]} err]} {
5601             puts "oops, can't read $f: $err"
5602             return
5603         }
5604     } else {
5605         set blob [lindex $treeidlist($diffids) $i]
5606         if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
5607             puts "oops, error reading blob $blob: $err"
5608             return
5609         }
5610     }
5611     fconfigure $bf -blocking 0
5612     filerun $bf [list getblobline $bf $diffids]
5613     $ctext config -state normal
5614     clear_ctext $commentend
5615     $ctext insert end "\n"
5616     $ctext insert end "$f\n" filesep
5617     $ctext config -state disabled
5618     $ctext yview $commentend
5619     settabs 0
5622 proc getblobline {bf id} {
5623     global diffids cmitmode ctext
5625     if {$id ne $diffids || $cmitmode ne "tree"} {
5626         catch {close $bf}
5627         return 0
5628     }
5629     $ctext config -state normal
5630     set nl 0
5631     while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
5632         $ctext insert end "$line\n"
5633     }
5634     if {[eof $bf]} {
5635         # delete last newline
5636         $ctext delete "end - 2c" "end - 1c"
5637         close $bf
5638         return 0
5639     }
5640     $ctext config -state disabled
5641     return [expr {$nl >= 1000? 2: 1}]
5644 proc mergediff {id} {
5645     global diffmergeid mdifffd
5646     global diffids
5647     global parents
5648     global limitdiffs viewfiles curview
5650     set diffmergeid $id
5651     set diffids $id
5652     # this doesn't seem to actually affect anything...
5653     set cmd [concat | git diff-tree --no-commit-id --cc $id]
5654     if {$limitdiffs && $viewfiles($curview) ne {}} {
5655         set cmd [concat $cmd -- $viewfiles($curview)]
5656     }
5657     if {[catch {set mdf [open $cmd r]} err]} {
5658         error_popup "Error getting merge diffs: $err"
5659         return
5660     }
5661     fconfigure $mdf -blocking 0
5662     set mdifffd($id) $mdf
5663     set np [llength $parents($curview,$id)]
5664     settabs $np
5665     filerun $mdf [list getmergediffline $mdf $id $np]
5668 proc getmergediffline {mdf id np} {
5669     global diffmergeid ctext cflist mergemax
5670     global difffilestart mdifffd
5672     $ctext conf -state normal
5673     set nr 0
5674     while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
5675         if {![info exists diffmergeid] || $id != $diffmergeid
5676             || $mdf != $mdifffd($id)} {
5677             close $mdf
5678             return 0
5679         }
5680         if {[regexp {^diff --cc (.*)} $line match fname]} {
5681             # start of a new file
5682             $ctext insert end "\n"
5683             set here [$ctext index "end - 1c"]
5684             lappend difffilestart $here
5685             add_flist [list $fname]
5686             set l [expr {(78 - [string length $fname]) / 2}]
5687             set pad [string range "----------------------------------------" 1 $l]
5688             $ctext insert end "$pad $fname $pad\n" filesep
5689         } elseif {[regexp {^@@} $line]} {
5690             $ctext insert end "$line\n" hunksep
5691         } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
5692             # do nothing
5693         } else {
5694             # parse the prefix - one ' ', '-' or '+' for each parent
5695             set spaces {}
5696             set minuses {}
5697             set pluses {}
5698             set isbad 0
5699             for {set j 0} {$j < $np} {incr j} {
5700                 set c [string range $line $j $j]
5701                 if {$c == " "} {
5702                     lappend spaces $j
5703                 } elseif {$c == "-"} {
5704                     lappend minuses $j
5705                 } elseif {$c == "+"} {
5706                     lappend pluses $j
5707                 } else {
5708                     set isbad 1
5709                     break
5710                 }
5711             }
5712             set tags {}
5713             set num {}
5714             if {!$isbad && $minuses ne {} && $pluses eq {}} {
5715                 # line doesn't appear in result, parents in $minuses have the line
5716                 set num [lindex $minuses 0]
5717             } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
5718                 # line appears in result, parents in $pluses don't have the line
5719                 lappend tags mresult
5720                 set num [lindex $spaces 0]
5721             }
5722             if {$num ne {}} {
5723                 if {$num >= $mergemax} {
5724                     set num "max"
5725                 }
5726                 lappend tags m$num
5727             }
5728             $ctext insert end "$line\n" $tags
5729         }
5730     }
5731     $ctext conf -state disabled
5732     if {[eof $mdf]} {
5733         close $mdf
5734         return 0
5735     }
5736     return [expr {$nr >= 1000? 2: 1}]
5739 proc startdiff {ids} {
5740     global treediffs diffids treepending diffmergeid nullid nullid2
5742     settabs 1
5743     set diffids $ids
5744     catch {unset diffmergeid}
5745     if {![info exists treediffs($ids)] ||
5746         [lsearch -exact $ids $nullid] >= 0 ||
5747         [lsearch -exact $ids $nullid2] >= 0} {
5748         if {![info exists treepending]} {
5749             gettreediffs $ids
5750         }
5751     } else {
5752         addtocflist $ids
5753     }
5756 proc path_filter {filter name} {
5757     foreach p $filter {
5758         set l [string length $p]
5759         if {[string index $p end] eq "/"} {
5760             if {[string compare -length $l $p $name] == 0} {
5761                 return 1
5762             }
5763         } else {
5764             if {[string compare -length $l $p $name] == 0 &&
5765                 ([string length $name] == $l ||
5766                  [string index $name $l] eq "/")} {
5767                 return 1
5768             }
5769         }
5770     }
5771     return 0
5774 proc addtocflist {ids} {
5775     global treediffs
5777     add_flist $treediffs($ids)
5778     getblobdiffs $ids
5781 proc diffcmd {ids flags} {
5782     global nullid nullid2
5784     set i [lsearch -exact $ids $nullid]
5785     set j [lsearch -exact $ids $nullid2]
5786     if {$i >= 0} {
5787         if {[llength $ids] > 1 && $j < 0} {
5788             # comparing working directory with some specific revision
5789             set cmd [concat | git diff-index $flags]
5790             if {$i == 0} {
5791                 lappend cmd -R [lindex $ids 1]
5792             } else {
5793                 lappend cmd [lindex $ids 0]
5794             }
5795         } else {
5796             # comparing working directory with index
5797             set cmd [concat | git diff-files $flags]
5798             if {$j == 1} {
5799                 lappend cmd -R
5800             }
5801         }
5802     } elseif {$j >= 0} {
5803         set cmd [concat | git diff-index --cached $flags]
5804         if {[llength $ids] > 1} {
5805             # comparing index with specific revision
5806             if {$i == 0} {
5807                 lappend cmd -R [lindex $ids 1]
5808             } else {
5809                 lappend cmd [lindex $ids 0]
5810             }
5811         } else {
5812             # comparing index with HEAD
5813             lappend cmd HEAD
5814         }
5815     } else {
5816         set cmd [concat | git diff-tree -r $flags $ids]
5817     }
5818     return $cmd
5821 proc gettreediffs {ids} {
5822     global treediff treepending
5824     set treepending $ids
5825     set treediff {}
5826     if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
5827     fconfigure $gdtf -blocking 0
5828     filerun $gdtf [list gettreediffline $gdtf $ids]
5831 proc gettreediffline {gdtf ids} {
5832     global treediff treediffs treepending diffids diffmergeid
5833     global cmitmode viewfiles curview limitdiffs
5835     set nr 0
5836     while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
5837         set i [string first "\t" $line]
5838         if {$i >= 0} {
5839             set file [string range $line [expr {$i+1}] end]
5840             if {[string index $file 0] eq "\""} {
5841                 set file [lindex $file 0]
5842             }
5843             lappend treediff $file
5844         }
5845     }
5846     if {![eof $gdtf]} {
5847         return [expr {$nr >= 1000? 2: 1}]
5848     }
5849     close $gdtf
5850     if {$limitdiffs && $viewfiles($curview) ne {}} {
5851         set flist {}
5852         foreach f $treediff {
5853             if {[path_filter $viewfiles($curview) $f]} {
5854                 lappend flist $f
5855             }
5856         }
5857         set treediffs($ids) $flist
5858     } else {
5859         set treediffs($ids) $treediff
5860     }
5861     unset treepending
5862     if {$cmitmode eq "tree"} {
5863         gettree $diffids
5864     } elseif {$ids != $diffids} {
5865         if {![info exists diffmergeid]} {
5866             gettreediffs $diffids
5867         }
5868     } else {
5869         addtocflist $ids
5870     }
5871     return 0
5874 # empty string or positive integer
5875 proc diffcontextvalidate {v} {
5876     return [regexp {^(|[1-9][0-9]*)$} $v]
5879 proc diffcontextchange {n1 n2 op} {
5880     global diffcontextstring diffcontext
5882     if {[string is integer -strict $diffcontextstring]} {
5883         if {$diffcontextstring > 0} {
5884             set diffcontext $diffcontextstring
5885             reselectline
5886         }
5887     }
5890 proc getblobdiffs {ids} {
5891     global blobdifffd diffids env
5892     global diffinhdr treediffs
5893     global diffcontext
5894     global limitdiffs viewfiles curview
5896     set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
5897     if {$limitdiffs && $viewfiles($curview) ne {}} {
5898         set cmd [concat $cmd -- $viewfiles($curview)]
5899     }
5900     if {[catch {set bdf [open $cmd r]} err]} {
5901         puts "error getting diffs: $err"
5902         return
5903     }
5904     set diffinhdr 0
5905     fconfigure $bdf -blocking 0
5906     set blobdifffd($ids) $bdf
5907     filerun $bdf [list getblobdiffline $bdf $diffids]
5910 proc setinlist {var i val} {
5911     global $var
5913     while {[llength [set $var]] < $i} {
5914         lappend $var {}
5915     }
5916     if {[llength [set $var]] == $i} {
5917         lappend $var $val
5918     } else {
5919         lset $var $i $val
5920     }
5923 proc makediffhdr {fname ids} {
5924     global ctext curdiffstart treediffs
5926     set i [lsearch -exact $treediffs($ids) $fname]
5927     if {$i >= 0} {
5928         setinlist difffilestart $i $curdiffstart
5929     }
5930     set l [expr {(78 - [string length $fname]) / 2}]
5931     set pad [string range "----------------------------------------" 1 $l]
5932     $ctext insert $curdiffstart "$pad $fname $pad" filesep
5935 proc getblobdiffline {bdf ids} {
5936     global diffids blobdifffd ctext curdiffstart
5937     global diffnexthead diffnextnote difffilestart
5938     global diffinhdr treediffs
5940     set nr 0
5941     $ctext conf -state normal
5942     while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
5943         if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
5944             close $bdf
5945             return 0
5946         }
5947         if {![string compare -length 11 "diff --git " $line]} {
5948             # trim off "diff --git "
5949             set line [string range $line 11 end]
5950             set diffinhdr 1
5951             # start of a new file
5952             $ctext insert end "\n"
5953             set curdiffstart [$ctext index "end - 1c"]
5954             $ctext insert end "\n" filesep
5955             # If the name hasn't changed the length will be odd,
5956             # the middle char will be a space, and the two bits either
5957             # side will be a/name and b/name, or "a/name" and "b/name".
5958             # If the name has changed we'll get "rename from" and
5959             # "rename to" or "copy from" and "copy to" lines following this,
5960             # and we'll use them to get the filenames.
5961             # This complexity is necessary because spaces in the filename(s)
5962             # don't get escaped.
5963             set l [string length $line]
5964             set i [expr {$l / 2}]
5965             if {!(($l & 1) && [string index $line $i] eq " " &&
5966                   [string range $line 2 [expr {$i - 1}]] eq \
5967                       [string range $line [expr {$i + 3}] end])} {
5968                 continue
5969             }
5970             # unescape if quoted and chop off the a/ from the front
5971             if {[string index $line 0] eq "\""} {
5972                 set fname [string range [lindex $line 0] 2 end]
5973             } else {
5974                 set fname [string range $line 2 [expr {$i - 1}]]
5975             }
5976             makediffhdr $fname $ids
5978         } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
5979                        $line match f1l f1c f2l f2c rest]} {
5980             $ctext insert end "$line\n" hunksep
5981             set diffinhdr 0
5983         } elseif {$diffinhdr} {
5984             if {![string compare -length 12 "rename from " $line]} {
5985                 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
5986                 if {[string index $fname 0] eq "\""} {
5987                     set fname [lindex $fname 0]
5988                 }
5989                 set i [lsearch -exact $treediffs($ids) $fname]
5990                 if {$i >= 0} {
5991                     setinlist difffilestart $i $curdiffstart
5992                 }
5993             } elseif {![string compare -length 10 $line "rename to "] ||
5994                       ![string compare -length 8 $line "copy to "]} {
5995                 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
5996                 if {[string index $fname 0] eq "\""} {
5997                     set fname [lindex $fname 0]
5998                 }
5999                 makediffhdr $fname $ids
6000             } elseif {[string compare -length 3 $line "---"] == 0} {
6001                 # do nothing
6002                 continue
6003             } elseif {[string compare -length 3 $line "+++"] == 0} {
6004                 set diffinhdr 0
6005                 continue
6006             }
6007             $ctext insert end "$line\n" filesep
6009         } else {
6010             set x [string range $line 0 0]
6011             if {$x == "-" || $x == "+"} {
6012                 set tag [expr {$x == "+"}]
6013                 $ctext insert end "$line\n" d$tag
6014             } elseif {$x == " "} {
6015                 $ctext insert end "$line\n"
6016             } else {
6017                 # "\ No newline at end of file",
6018                 # or something else we don't recognize
6019                 $ctext insert end "$line\n" hunksep
6020             }
6021         }
6022     }
6023     $ctext conf -state disabled
6024     if {[eof $bdf]} {
6025         close $bdf
6026         return 0
6027     }
6028     return [expr {$nr >= 1000? 2: 1}]
6031 proc changediffdisp {} {
6032     global ctext diffelide
6034     $ctext tag conf d0 -elide [lindex $diffelide 0]
6035     $ctext tag conf d1 -elide [lindex $diffelide 1]
6038 proc prevfile {} {
6039     global difffilestart ctext
6040     set prev [lindex $difffilestart 0]
6041     set here [$ctext index @0,0]
6042     foreach loc $difffilestart {
6043         if {[$ctext compare $loc >= $here]} {
6044             $ctext yview $prev
6045             return
6046         }
6047         set prev $loc
6048     }
6049     $ctext yview $prev
6052 proc nextfile {} {
6053     global difffilestart ctext
6054     set here [$ctext index @0,0]
6055     foreach loc $difffilestart {
6056         if {[$ctext compare $loc > $here]} {
6057             $ctext yview $loc
6058             return
6059         }
6060     }
6063 proc clear_ctext {{first 1.0}} {
6064     global ctext smarktop smarkbot
6065     global pendinglinks
6067     set l [lindex [split $first .] 0]
6068     if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
6069         set smarktop $l
6070     }
6071     if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
6072         set smarkbot $l
6073     }
6074     $ctext delete $first end
6075     if {$first eq "1.0"} {
6076         catch {unset pendinglinks}
6077     }
6080 proc settabs {{firstab {}}} {
6081     global firsttabstop tabstop ctext have_tk85
6083     if {$firstab ne {} && $have_tk85} {
6084         set firsttabstop $firstab
6085     }
6086     set w [font measure textfont "0"]
6087     if {$firsttabstop != 0} {
6088         $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
6089                                [expr {($firsttabstop + 2 * $tabstop) * $w}]]
6090     } elseif {$have_tk85 || $tabstop != 8} {
6091         $ctext conf -tabs [expr {$tabstop * $w}]
6092     } else {
6093         $ctext conf -tabs {}
6094     }
6097 proc incrsearch {name ix op} {
6098     global ctext searchstring searchdirn
6100     $ctext tag remove found 1.0 end
6101     if {[catch {$ctext index anchor}]} {
6102         # no anchor set, use start of selection, or of visible area
6103         set sel [$ctext tag ranges sel]
6104         if {$sel ne {}} {
6105             $ctext mark set anchor [lindex $sel 0]
6106         } elseif {$searchdirn eq "-forwards"} {
6107             $ctext mark set anchor @0,0
6108         } else {
6109             $ctext mark set anchor @0,[winfo height $ctext]
6110         }
6111     }
6112     if {$searchstring ne {}} {
6113         set here [$ctext search $searchdirn -- $searchstring anchor]
6114         if {$here ne {}} {
6115             $ctext see $here
6116         }
6117         searchmarkvisible 1
6118     }
6121 proc dosearch {} {
6122     global sstring ctext searchstring searchdirn
6124     focus $sstring
6125     $sstring icursor end
6126     set searchdirn -forwards
6127     if {$searchstring ne {}} {
6128         set sel [$ctext tag ranges sel]
6129         if {$sel ne {}} {
6130             set start "[lindex $sel 0] + 1c"
6131         } elseif {[catch {set start [$ctext index anchor]}]} {
6132             set start "@0,0"
6133         }
6134         set match [$ctext search -count mlen -- $searchstring $start]
6135         $ctext tag remove sel 1.0 end
6136         if {$match eq {}} {
6137             bell
6138             return
6139         }
6140         $ctext see $match
6141         set mend "$match + $mlen c"
6142         $ctext tag add sel $match $mend
6143         $ctext mark unset anchor
6144     }
6147 proc dosearchback {} {
6148     global sstring ctext searchstring searchdirn
6150     focus $sstring
6151     $sstring icursor end
6152     set searchdirn -backwards
6153     if {$searchstring ne {}} {
6154         set sel [$ctext tag ranges sel]
6155         if {$sel ne {}} {
6156             set start [lindex $sel 0]
6157         } elseif {[catch {set start [$ctext index anchor]}]} {
6158             set start @0,[winfo height $ctext]
6159         }
6160         set match [$ctext search -backwards -count ml -- $searchstring $start]
6161         $ctext tag remove sel 1.0 end
6162         if {$match eq {}} {
6163             bell
6164             return
6165         }
6166         $ctext see $match
6167         set mend "$match + $ml c"
6168         $ctext tag add sel $match $mend
6169         $ctext mark unset anchor
6170     }
6173 proc searchmark {first last} {
6174     global ctext searchstring
6176     set mend $first.0
6177     while {1} {
6178         set match [$ctext search -count mlen -- $searchstring $mend $last.end]
6179         if {$match eq {}} break
6180         set mend "$match + $mlen c"
6181         $ctext tag add found $match $mend
6182     }
6185 proc searchmarkvisible {doall} {
6186     global ctext smarktop smarkbot
6188     set topline [lindex [split [$ctext index @0,0] .] 0]
6189     set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
6190     if {$doall || $botline < $smarktop || $topline > $smarkbot} {
6191         # no overlap with previous
6192         searchmark $topline $botline
6193         set smarktop $topline
6194         set smarkbot $botline
6195     } else {
6196         if {$topline < $smarktop} {
6197             searchmark $topline [expr {$smarktop-1}]
6198             set smarktop $topline
6199         }
6200         if {$botline > $smarkbot} {
6201             searchmark [expr {$smarkbot+1}] $botline
6202             set smarkbot $botline
6203         }
6204     }
6207 proc scrolltext {f0 f1} {
6208     global searchstring
6210     .bleft.sb set $f0 $f1
6211     if {$searchstring ne {}} {
6212         searchmarkvisible 0
6213     }
6216 proc setcoords {} {
6217     global linespc charspc canvx0 canvy0
6218     global xspc1 xspc2 lthickness
6220     set linespc [font metrics mainfont -linespace]
6221     set charspc [font measure mainfont "m"]
6222     set canvy0 [expr {int(3 + 0.5 * $linespc)}]
6223     set canvx0 [expr {int(3 + 0.5 * $linespc)}]
6224     set lthickness [expr {int($linespc / 9) + 1}]
6225     set xspc1(0) $linespc
6226     set xspc2 $linespc
6229 proc redisplay {} {
6230     global canv
6231     global selectedline
6233     set ymax [lindex [$canv cget -scrollregion] 3]
6234     if {$ymax eq {} || $ymax == 0} return
6235     set span [$canv yview]
6236     clear_display
6237     setcanvscroll
6238     allcanvs yview moveto [lindex $span 0]
6239     drawvisible
6240     if {[info exists selectedline]} {
6241         selectline $selectedline 0
6242         allcanvs yview moveto [lindex $span 0]
6243     }
6246 proc parsefont {f n} {
6247     global fontattr
6249     set fontattr($f,family) [lindex $n 0]
6250     set s [lindex $n 1]
6251     if {$s eq {} || $s == 0} {
6252         set s 10
6253     } elseif {$s < 0} {
6254         set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
6255     }
6256     set fontattr($f,size) $s
6257     set fontattr($f,weight) normal
6258     set fontattr($f,slant) roman
6259     foreach style [lrange $n 2 end] {
6260         switch -- $style {
6261             "normal" -
6262             "bold"   {set fontattr($f,weight) $style}
6263             "roman" -
6264             "italic" {set fontattr($f,slant) $style}
6265         }
6266     }
6269 proc fontflags {f {isbold 0}} {
6270     global fontattr
6272     return [list -family $fontattr($f,family) -size $fontattr($f,size) \
6273                 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
6274                 -slant $fontattr($f,slant)]
6277 proc fontname {f} {
6278     global fontattr
6280     set n [list $fontattr($f,family) $fontattr($f,size)]
6281     if {$fontattr($f,weight) eq "bold"} {
6282         lappend n "bold"
6283     }
6284     if {$fontattr($f,slant) eq "italic"} {
6285         lappend n "italic"
6286     }
6287     return $n
6290 proc incrfont {inc} {
6291     global mainfont textfont ctext canv cflist showrefstop
6292     global stopped entries fontattr
6294     unmarkmatches
6295     set s $fontattr(mainfont,size)
6296     incr s $inc
6297     if {$s < 1} {
6298         set s 1
6299     }
6300     set fontattr(mainfont,size) $s
6301     font config mainfont -size $s
6302     font config mainfontbold -size $s
6303     set mainfont [fontname mainfont]
6304     set s $fontattr(textfont,size)
6305     incr s $inc
6306     if {$s < 1} {
6307         set s 1
6308     }
6309     set fontattr(textfont,size) $s
6310     font config textfont -size $s
6311     font config textfontbold -size $s
6312     set textfont [fontname textfont]
6313     setcoords
6314     settabs
6315     redisplay
6318 proc clearsha1 {} {
6319     global sha1entry sha1string
6320     if {[string length $sha1string] == 40} {
6321         $sha1entry delete 0 end
6322     }
6325 proc sha1change {n1 n2 op} {
6326     global sha1string currentid sha1but
6327     if {$sha1string == {}
6328         || ([info exists currentid] && $sha1string == $currentid)} {
6329         set state disabled
6330     } else {
6331         set state normal
6332     }
6333     if {[$sha1but cget -state] == $state} return
6334     if {$state == "normal"} {
6335         $sha1but conf -state normal -relief raised -text "Goto: "
6336     } else {
6337         $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
6338     }
6341 proc gotocommit {} {
6342     global sha1string tagids headids curview varcid
6344     if {$sha1string == {}
6345         || ([info exists currentid] && $sha1string == $currentid)} return
6346     if {[info exists tagids($sha1string)]} {
6347         set id $tagids($sha1string)
6348     } elseif {[info exists headids($sha1string)]} {
6349         set id $headids($sha1string)
6350     } else {
6351         set id [string tolower $sha1string]
6352         if {[regexp {^[0-9a-f]{4,39}$} $id]} {
6353             set matches [array names varcid "$curview,$id*"]
6354             if {$matches ne {}} {
6355                 if {[llength $matches] > 1} {
6356                     error_popup "Short SHA1 id $id is ambiguous"
6357                     return
6358                 }
6359                 set id [lindex [split [lindex $matches 0] ","] 1]
6360             }
6361         }
6362     }
6363     if {[commitinview $id $curview]} {
6364         selectline [rowofcommit $id] 1
6365         return
6366     }
6367     if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
6368         set type "SHA1 id"
6369     } else {
6370         set type "Tag/Head"
6371     }
6372     error_popup "$type $sha1string is not known"
6375 proc lineenter {x y id} {
6376     global hoverx hovery hoverid hovertimer
6377     global commitinfo canv
6379     if {![info exists commitinfo($id)] && ![getcommit $id]} return
6380     set hoverx $x
6381     set hovery $y
6382     set hoverid $id
6383     if {[info exists hovertimer]} {
6384         after cancel $hovertimer
6385     }
6386     set hovertimer [after 500 linehover]
6387     $canv delete hover
6390 proc linemotion {x y id} {
6391     global hoverx hovery hoverid hovertimer
6393     if {[info exists hoverid] && $id == $hoverid} {
6394         set hoverx $x
6395         set hovery $y
6396         if {[info exists hovertimer]} {
6397             after cancel $hovertimer
6398         }
6399         set hovertimer [after 500 linehover]
6400     }
6403 proc lineleave {id} {
6404     global hoverid hovertimer canv
6406     if {[info exists hoverid] && $id == $hoverid} {
6407         $canv delete hover
6408         if {[info exists hovertimer]} {
6409             after cancel $hovertimer
6410             unset hovertimer
6411         }
6412         unset hoverid
6413     }
6416 proc linehover {} {
6417     global hoverx hovery hoverid hovertimer
6418     global canv linespc lthickness
6419     global commitinfo
6421     set text [lindex $commitinfo($hoverid) 0]
6422     set ymax [lindex [$canv cget -scrollregion] 3]
6423     if {$ymax == {}} return
6424     set yfrac [lindex [$canv yview] 0]
6425     set x [expr {$hoverx + 2 * $linespc}]
6426     set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
6427     set x0 [expr {$x - 2 * $lthickness}]
6428     set y0 [expr {$y - 2 * $lthickness}]
6429     set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
6430     set y1 [expr {$y + $linespc + 2 * $lthickness}]
6431     set t [$canv create rectangle $x0 $y0 $x1 $y1 \
6432                -fill \#ffff80 -outline black -width 1 -tags hover]
6433     $canv raise $t
6434     set t [$canv create text $x $y -anchor nw -text $text -tags hover \
6435                -font mainfont]
6436     $canv raise $t
6439 proc clickisonarrow {id y} {
6440     global lthickness
6442     set ranges [rowranges $id]
6443     set thresh [expr {2 * $lthickness + 6}]
6444     set n [expr {[llength $ranges] - 1}]
6445     for {set i 1} {$i < $n} {incr i} {
6446         set row [lindex $ranges $i]
6447         if {abs([yc $row] - $y) < $thresh} {
6448             return $i
6449         }
6450     }
6451     return {}
6454 proc arrowjump {id n y} {
6455     global canv
6457     # 1 <-> 2, 3 <-> 4, etc...
6458     set n [expr {(($n - 1) ^ 1) + 1}]
6459     set row [lindex [rowranges $id] $n]
6460     set yt [yc $row]
6461     set ymax [lindex [$canv cget -scrollregion] 3]
6462     if {$ymax eq {} || $ymax <= 0} return
6463     set view [$canv yview]
6464     set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
6465     set yfrac [expr {$yt / $ymax - $yspan / 2}]
6466     if {$yfrac < 0} {
6467         set yfrac 0
6468     }
6469     allcanvs yview moveto $yfrac
6472 proc lineclick {x y id isnew} {
6473     global ctext commitinfo children canv thickerline curview
6475     if {![info exists commitinfo($id)] && ![getcommit $id]} return
6476     unmarkmatches
6477     unselectline
6478     normalline
6479     $canv delete hover
6480     # draw this line thicker than normal
6481     set thickerline $id
6482     drawlines $id
6483     if {$isnew} {
6484         set ymax [lindex [$canv cget -scrollregion] 3]
6485         if {$ymax eq {}} return
6486         set yfrac [lindex [$canv yview] 0]
6487         set y [expr {$y + $yfrac * $ymax}]
6488     }
6489     set dirn [clickisonarrow $id $y]
6490     if {$dirn ne {}} {
6491         arrowjump $id $dirn $y
6492         return
6493     }
6495     if {$isnew} {
6496         addtohistory [list lineclick $x $y $id 0]
6497     }
6498     # fill the details pane with info about this line
6499     $ctext conf -state normal
6500     clear_ctext
6501     settabs 0
6502     $ctext insert end "Parent:\t"
6503     $ctext insert end $id link0
6504     setlink $id link0
6505     set info $commitinfo($id)
6506     $ctext insert end "\n\t[lindex $info 0]\n"
6507     $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
6508     set date [formatdate [lindex $info 2]]
6509     $ctext insert end "\tDate:\t$date\n"
6510     set kids $children($curview,$id)
6511     if {$kids ne {}} {
6512         $ctext insert end "\nChildren:"
6513         set i 0
6514         foreach child $kids {
6515             incr i
6516             if {![info exists commitinfo($child)] && ![getcommit $child]} continue
6517             set info $commitinfo($child)
6518             $ctext insert end "\n\t"
6519             $ctext insert end $child link$i
6520             setlink $child link$i
6521             $ctext insert end "\n\t[lindex $info 0]"
6522             $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
6523             set date [formatdate [lindex $info 2]]
6524             $ctext insert end "\n\tDate:\t$date\n"
6525         }
6526     }
6527     $ctext conf -state disabled
6528     init_flist {}
6531 proc normalline {} {
6532     global thickerline
6533     if {[info exists thickerline]} {
6534         set id $thickerline
6535         unset thickerline
6536         drawlines $id
6537     }
6540 proc selbyid {id} {
6541     global curview
6542     if {[commitinview $id $curview]} {
6543         selectline [rowofcommit $id] 1
6544     }
6547 proc mstime {} {
6548     global startmstime
6549     if {![info exists startmstime]} {
6550         set startmstime [clock clicks -milliseconds]
6551     }
6552     return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
6555 proc rowmenu {x y id} {
6556     global rowctxmenu selectedline rowmenuid curview
6557     global nullid nullid2 fakerowmenu mainhead
6559     stopfinding
6560     set rowmenuid $id
6561     if {![info exists selectedline]
6562         || [rowofcommit $id] eq $selectedline} {
6563         set state disabled
6564     } else {
6565         set state normal
6566     }
6567     if {$id ne $nullid && $id ne $nullid2} {
6568         set menu $rowctxmenu
6569         $menu entryconfigure 7 -label "Reset $mainhead branch to here"
6570     } else {
6571         set menu $fakerowmenu
6572     }
6573     $menu entryconfigure "Diff this*" -state $state
6574     $menu entryconfigure "Diff selected*" -state $state
6575     $menu entryconfigure "Make patch" -state $state
6576     tk_popup $menu $x $y
6579 proc diffvssel {dirn} {
6580     global rowmenuid selectedline
6582     if {![info exists selectedline]} return
6583     if {$dirn} {
6584         set oldid [commitonrow $selectedline]
6585         set newid $rowmenuid
6586     } else {
6587         set oldid $rowmenuid
6588         set newid [commitonrow $selectedline]
6589     }
6590     addtohistory [list doseldiff $oldid $newid]
6591     doseldiff $oldid $newid
6594 proc doseldiff {oldid newid} {
6595     global ctext
6596     global commitinfo
6598     $ctext conf -state normal
6599     clear_ctext
6600     init_flist "Top"
6601     $ctext insert end "From "
6602     $ctext insert end $oldid link0
6603     setlink $oldid link0
6604     $ctext insert end "\n     "
6605     $ctext insert end [lindex $commitinfo($oldid) 0]
6606     $ctext insert end "\n\nTo   "
6607     $ctext insert end $newid link1
6608     setlink $newid link1
6609     $ctext insert end "\n     "
6610     $ctext insert end [lindex $commitinfo($newid) 0]
6611     $ctext insert end "\n"
6612     $ctext conf -state disabled
6613     $ctext tag remove found 1.0 end
6614     startdiff [list $oldid $newid]
6617 proc mkpatch {} {
6618     global rowmenuid currentid commitinfo patchtop patchnum
6620     if {![info exists currentid]} return
6621     set oldid $currentid
6622     set oldhead [lindex $commitinfo($oldid) 0]
6623     set newid $rowmenuid
6624     set newhead [lindex $commitinfo($newid) 0]
6625     set top .patch
6626     set patchtop $top
6627     catch {destroy $top}
6628     toplevel $top
6629     label $top.title -text "Generate patch"
6630     grid $top.title - -pady 10
6631     label $top.from -text "From:"
6632     entry $top.fromsha1 -width 40 -relief flat
6633     $top.fromsha1 insert 0 $oldid
6634     $top.fromsha1 conf -state readonly
6635     grid $top.from $top.fromsha1 -sticky w
6636     entry $top.fromhead -width 60 -relief flat
6637     $top.fromhead insert 0 $oldhead
6638     $top.fromhead conf -state readonly
6639     grid x $top.fromhead -sticky w
6640     label $top.to -text "To:"
6641     entry $top.tosha1 -width 40 -relief flat
6642     $top.tosha1 insert 0 $newid
6643     $top.tosha1 conf -state readonly
6644     grid $top.to $top.tosha1 -sticky w
6645     entry $top.tohead -width 60 -relief flat
6646     $top.tohead insert 0 $newhead
6647     $top.tohead conf -state readonly
6648     grid x $top.tohead -sticky w
6649     button $top.rev -text "Reverse" -command mkpatchrev -padx 5
6650     grid $top.rev x -pady 10
6651     label $top.flab -text "Output file:"
6652     entry $top.fname -width 60
6653     $top.fname insert 0 [file normalize "patch$patchnum.patch"]
6654     incr patchnum
6655     grid $top.flab $top.fname -sticky w
6656     frame $top.buts
6657     button $top.buts.gen -text "Generate" -command mkpatchgo
6658     button $top.buts.can -text "Cancel" -command mkpatchcan
6659     grid $top.buts.gen $top.buts.can
6660     grid columnconfigure $top.buts 0 -weight 1 -uniform a
6661     grid columnconfigure $top.buts 1 -weight 1 -uniform a
6662     grid $top.buts - -pady 10 -sticky ew
6663     focus $top.fname
6666 proc mkpatchrev {} {
6667     global patchtop
6669     set oldid [$patchtop.fromsha1 get]
6670     set oldhead [$patchtop.fromhead get]
6671     set newid [$patchtop.tosha1 get]
6672     set newhead [$patchtop.tohead get]
6673     foreach e [list fromsha1 fromhead tosha1 tohead] \
6674             v [list $newid $newhead $oldid $oldhead] {
6675         $patchtop.$e conf -state normal
6676         $patchtop.$e delete 0 end
6677         $patchtop.$e insert 0 $v
6678         $patchtop.$e conf -state readonly
6679     }
6682 proc mkpatchgo {} {
6683     global patchtop nullid nullid2
6685     set oldid [$patchtop.fromsha1 get]
6686     set newid [$patchtop.tosha1 get]
6687     set fname [$patchtop.fname get]
6688     set cmd [diffcmd [list $oldid $newid] -p]
6689     # trim off the initial "|"
6690     set cmd [lrange $cmd 1 end]
6691     lappend cmd >$fname &
6692     if {[catch {eval exec $cmd} err]} {
6693         error_popup "Error creating patch: $err"
6694     }
6695     catch {destroy $patchtop}
6696     unset patchtop
6699 proc mkpatchcan {} {
6700     global patchtop
6702     catch {destroy $patchtop}
6703     unset patchtop
6706 proc mktag {} {
6707     global rowmenuid mktagtop commitinfo
6709     set top .maketag
6710     set mktagtop $top
6711     catch {destroy $top}
6712     toplevel $top
6713     label $top.title -text "Create tag"
6714     grid $top.title - -pady 10
6715     label $top.id -text "ID:"
6716     entry $top.sha1 -width 40 -relief flat
6717     $top.sha1 insert 0 $rowmenuid
6718     $top.sha1 conf -state readonly
6719     grid $top.id $top.sha1 -sticky w
6720     entry $top.head -width 60 -relief flat
6721     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
6722     $top.head conf -state readonly
6723     grid x $top.head -sticky w
6724     label $top.tlab -text "Tag name:"
6725     entry $top.tag -width 60
6726     grid $top.tlab $top.tag -sticky w
6727     frame $top.buts
6728     button $top.buts.gen -text "Create" -command mktaggo
6729     button $top.buts.can -text "Cancel" -command mktagcan
6730     grid $top.buts.gen $top.buts.can
6731     grid columnconfigure $top.buts 0 -weight 1 -uniform a
6732     grid columnconfigure $top.buts 1 -weight 1 -uniform a
6733     grid $top.buts - -pady 10 -sticky ew
6734     focus $top.tag
6737 proc domktag {} {
6738     global mktagtop env tagids idtags
6740     set id [$mktagtop.sha1 get]
6741     set tag [$mktagtop.tag get]
6742     if {$tag == {}} {
6743         error_popup "No tag name specified"
6744         return
6745     }
6746     if {[info exists tagids($tag)]} {
6747         error_popup "Tag \"$tag\" already exists"
6748         return
6749     }
6750     if {[catch {
6751         set dir [gitdir]
6752         set fname [file join $dir "refs/tags" $tag]
6753         set f [open $fname w]
6754         puts $f $id
6755         close $f
6756     } err]} {
6757         error_popup "Error creating tag: $err"
6758         return
6759     }
6761     set tagids($tag) $id
6762     lappend idtags($id) $tag
6763     redrawtags $id
6764     addedtag $id
6765     dispneartags 0
6766     run refill_reflist
6769 proc redrawtags {id} {
6770     global canv linehtag idpos selectedline curview
6771     global canvxmax iddrawn
6773     if {![commitinview $id $curview]} return
6774     if {![info exists iddrawn($id)]} return
6775     drawcommits [rowofcommit $id]
6776     $canv delete tag.$id
6777     set xt [eval drawtags $id $idpos($id)]
6778     $canv coords $linehtag([rowofcommit $id]) $xt [lindex $idpos($id) 2]
6779     set text [$canv itemcget $linehtag([rowofcommit $id]) -text]
6780     set xr [expr {$xt + [font measure mainfont $text]}]
6781     if {$xr > $canvxmax} {
6782         set canvxmax $xr
6783         setcanvscroll
6784     }
6785     if {[info exists selectedline]
6786         && $selectedline == [rowofcommit $id]} {
6787         selectline $selectedline 0
6788     }
6791 proc mktagcan {} {
6792     global mktagtop
6794     catch {destroy $mktagtop}
6795     unset mktagtop
6798 proc mktaggo {} {
6799     domktag
6800     mktagcan
6803 proc writecommit {} {
6804     global rowmenuid wrcomtop commitinfo wrcomcmd
6806     set top .writecommit
6807     set wrcomtop $top
6808     catch {destroy $top}
6809     toplevel $top
6810     label $top.title -text "Write commit to file"
6811     grid $top.title - -pady 10
6812     label $top.id -text "ID:"
6813     entry $top.sha1 -width 40 -relief flat
6814     $top.sha1 insert 0 $rowmenuid
6815     $top.sha1 conf -state readonly
6816     grid $top.id $top.sha1 -sticky w
6817     entry $top.head -width 60 -relief flat
6818     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
6819     $top.head conf -state readonly
6820     grid x $top.head -sticky w
6821     label $top.clab -text "Command:"
6822     entry $top.cmd -width 60 -textvariable wrcomcmd
6823     grid $top.clab $top.cmd -sticky w -pady 10
6824     label $top.flab -text "Output file:"
6825     entry $top.fname -width 60
6826     $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
6827     grid $top.flab $top.fname -sticky w
6828     frame $top.buts
6829     button $top.buts.gen -text "Write" -command wrcomgo
6830     button $top.buts.can -text "Cancel" -command wrcomcan
6831     grid $top.buts.gen $top.buts.can
6832     grid columnconfigure $top.buts 0 -weight 1 -uniform a
6833     grid columnconfigure $top.buts 1 -weight 1 -uniform a
6834     grid $top.buts - -pady 10 -sticky ew
6835     focus $top.fname
6838 proc wrcomgo {} {
6839     global wrcomtop
6841     set id [$wrcomtop.sha1 get]
6842     set cmd "echo $id | [$wrcomtop.cmd get]"
6843     set fname [$wrcomtop.fname get]
6844     if {[catch {exec sh -c $cmd >$fname &} err]} {
6845         error_popup "Error writing commit: $err"
6846     }
6847     catch {destroy $wrcomtop}
6848     unset wrcomtop
6851 proc wrcomcan {} {
6852     global wrcomtop
6854     catch {destroy $wrcomtop}
6855     unset wrcomtop
6858 proc mkbranch {} {
6859     global rowmenuid mkbrtop
6861     set top .makebranch
6862     catch {destroy $top}
6863     toplevel $top
6864     label $top.title -text "Create new branch"
6865     grid $top.title - -pady 10
6866     label $top.id -text "ID:"
6867     entry $top.sha1 -width 40 -relief flat
6868     $top.sha1 insert 0 $rowmenuid
6869     $top.sha1 conf -state readonly
6870     grid $top.id $top.sha1 -sticky w
6871     label $top.nlab -text "Name:"
6872     entry $top.name -width 40
6873     grid $top.nlab $top.name -sticky w
6874     frame $top.buts
6875     button $top.buts.go -text "Create" -command [list mkbrgo $top]
6876     button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
6877     grid $top.buts.go $top.buts.can
6878     grid columnconfigure $top.buts 0 -weight 1 -uniform a
6879     grid columnconfigure $top.buts 1 -weight 1 -uniform a
6880     grid $top.buts - -pady 10 -sticky ew
6881     focus $top.name
6884 proc mkbrgo {top} {
6885     global headids idheads
6887     set name [$top.name get]
6888     set id [$top.sha1 get]
6889     if {$name eq {}} {
6890         error_popup "Please specify a name for the new branch"
6891         return
6892     }
6893     catch {destroy $top}
6894     nowbusy newbranch
6895     update
6896     if {[catch {
6897         exec git branch $name $id
6898     } err]} {
6899         notbusy newbranch
6900         error_popup $err
6901     } else {
6902         set headids($name) $id
6903         lappend idheads($id) $name
6904         addedhead $id $name
6905         notbusy newbranch
6906         redrawtags $id
6907         dispneartags 0
6908         run refill_reflist
6909     }
6912 proc cherrypick {} {
6913     global rowmenuid curview
6914     global mainhead
6916     set oldhead [exec git rev-parse HEAD]
6917     set dheads [descheads $rowmenuid]
6918     if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
6919         set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
6920                         included in branch $mainhead -- really re-apply it?"]
6921         if {!$ok} return
6922     }
6923     nowbusy cherrypick "Cherry-picking"
6924     update
6925     # Unfortunately git-cherry-pick writes stuff to stderr even when
6926     # no error occurs, and exec takes that as an indication of error...
6927     if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
6928         notbusy cherrypick
6929         error_popup $err
6930         return
6931     }
6932     set newhead [exec git rev-parse HEAD]
6933     if {$newhead eq $oldhead} {
6934         notbusy cherrypick
6935         error_popup "No changes committed"
6936         return
6937     }
6938     addnewchild $newhead $oldhead
6939     if {[commitinview $oldhead $curview]} {
6940         insertrow $newhead $oldhead $curview
6941         if {$mainhead ne {}} {
6942             movehead $newhead $mainhead
6943             movedhead $newhead $mainhead
6944         }
6945         redrawtags $oldhead
6946         redrawtags $newhead
6947     }
6948     notbusy cherrypick
6951 proc resethead {} {
6952     global mainheadid mainhead rowmenuid confirm_ok resettype
6954     set confirm_ok 0
6955     set w ".confirmreset"
6956     toplevel $w
6957     wm transient $w .
6958     wm title $w "Confirm reset"
6959     message $w.m -text \
6960         "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
6961         -justify center -aspect 1000
6962     pack $w.m -side top -fill x -padx 20 -pady 20
6963     frame $w.f -relief sunken -border 2
6964     message $w.f.rt -text "Reset type:" -aspect 1000
6965     grid $w.f.rt -sticky w
6966     set resettype mixed
6967     radiobutton $w.f.soft -value soft -variable resettype -justify left \
6968         -text "Soft: Leave working tree and index untouched"
6969     grid $w.f.soft -sticky w
6970     radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
6971         -text "Mixed: Leave working tree untouched, reset index"
6972     grid $w.f.mixed -sticky w
6973     radiobutton $w.f.hard -value hard -variable resettype -justify left \
6974         -text "Hard: Reset working tree and index\n(discard ALL local changes)"
6975     grid $w.f.hard -sticky w
6976     pack $w.f -side top -fill x
6977     button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
6978     pack $w.ok -side left -fill x -padx 20 -pady 20
6979     button $w.cancel -text Cancel -command "destroy $w"
6980     pack $w.cancel -side right -fill x -padx 20 -pady 20
6981     bind $w <Visibility> "grab $w; focus $w"
6982     tkwait window $w
6983     if {!$confirm_ok} return
6984     if {[catch {set fd [open \
6985             [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
6986         error_popup $err
6987     } else {
6988         dohidelocalchanges
6989         filerun $fd [list readresetstat $fd]
6990         nowbusy reset "Resetting"
6991     }
6994 proc readresetstat {fd} {
6995     global mainhead mainheadid showlocalchanges rprogcoord
6997     if {[gets $fd line] >= 0} {
6998         if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
6999             set rprogcoord [expr {1.0 * $m / $n}]
7000             adjustprogress
7001         }
7002         return 1
7003     }
7004     set rprogcoord 0
7005     adjustprogress
7006     notbusy reset
7007     if {[catch {close $fd} err]} {
7008         error_popup $err
7009     }
7010     set oldhead $mainheadid
7011     set newhead [exec git rev-parse HEAD]
7012     if {$newhead ne $oldhead} {
7013         movehead $newhead $mainhead
7014         movedhead $newhead $mainhead
7015         set mainheadid $newhead
7016         redrawtags $oldhead
7017         redrawtags $newhead
7018     }
7019     if {$showlocalchanges} {
7020         doshowlocalchanges
7021     }
7022     return 0
7025 # context menu for a head
7026 proc headmenu {x y id head} {
7027     global headmenuid headmenuhead headctxmenu mainhead
7029     stopfinding
7030     set headmenuid $id
7031     set headmenuhead $head
7032     set state normal
7033     if {$head eq $mainhead} {
7034         set state disabled
7035     }
7036     $headctxmenu entryconfigure 0 -state $state
7037     $headctxmenu entryconfigure 1 -state $state
7038     tk_popup $headctxmenu $x $y
7041 proc cobranch {} {
7042     global headmenuid headmenuhead mainhead headids
7043     global showlocalchanges mainheadid
7045     # check the tree is clean first??
7046     set oldmainhead $mainhead
7047     nowbusy checkout "Checking out"
7048     update
7049     dohidelocalchanges
7050     if {[catch {
7051         exec git checkout -q $headmenuhead
7052     } err]} {
7053         notbusy checkout
7054         error_popup $err
7055     } else {
7056         notbusy checkout
7057         set mainhead $headmenuhead
7058         set mainheadid $headmenuid
7059         if {[info exists headids($oldmainhead)]} {
7060             redrawtags $headids($oldmainhead)
7061         }
7062         redrawtags $headmenuid
7063     }
7064     if {$showlocalchanges} {
7065         dodiffindex
7066     }
7069 proc rmbranch {} {
7070     global headmenuid headmenuhead mainhead
7071     global idheads
7073     set head $headmenuhead
7074     set id $headmenuid
7075     # this check shouldn't be needed any more...
7076     if {$head eq $mainhead} {
7077         error_popup "Cannot delete the currently checked-out branch"
7078         return
7079     }
7080     set dheads [descheads $id]
7081     if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
7082         # the stuff on this branch isn't on any other branch
7083         if {![confirm_popup "The commits on branch $head aren't on any other\
7084                         branch.\nReally delete branch $head?"]} return
7085     }
7086     nowbusy rmbranch
7087     update
7088     if {[catch {exec git branch -D $head} err]} {
7089         notbusy rmbranch
7090         error_popup $err
7091         return
7092     }
7093     removehead $id $head
7094     removedhead $id $head
7095     redrawtags $id
7096     notbusy rmbranch
7097     dispneartags 0
7098     run refill_reflist
7101 # Display a list of tags and heads
7102 proc showrefs {} {
7103     global showrefstop bgcolor fgcolor selectbgcolor
7104     global bglist fglist reflistfilter reflist maincursor
7106     set top .showrefs
7107     set showrefstop $top
7108     if {[winfo exists $top]} {
7109         raise $top
7110         refill_reflist
7111         return
7112     }
7113     toplevel $top
7114     wm title $top "Tags and heads: [file tail [pwd]]"
7115     text $top.list -background $bgcolor -foreground $fgcolor \
7116         -selectbackground $selectbgcolor -font mainfont \
7117         -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
7118         -width 30 -height 20 -cursor $maincursor \
7119         -spacing1 1 -spacing3 1 -state disabled
7120     $top.list tag configure highlight -background $selectbgcolor
7121     lappend bglist $top.list
7122     lappend fglist $top.list
7123     scrollbar $top.ysb -command "$top.list yview" -orient vertical
7124     scrollbar $top.xsb -command "$top.list xview" -orient horizontal
7125     grid $top.list $top.ysb -sticky nsew
7126     grid $top.xsb x -sticky ew
7127     frame $top.f
7128     label $top.f.l -text "Filter: " -font uifont
7129     entry $top.f.e -width 20 -textvariable reflistfilter -font uifont
7130     set reflistfilter "*"
7131     trace add variable reflistfilter write reflistfilter_change
7132     pack $top.f.e -side right -fill x -expand 1
7133     pack $top.f.l -side left
7134     grid $top.f - -sticky ew -pady 2
7135     button $top.close -command [list destroy $top] -text "Close" \
7136         -font uifont
7137     grid $top.close -
7138     grid columnconfigure $top 0 -weight 1
7139     grid rowconfigure $top 0 -weight 1
7140     bind $top.list <1> {break}
7141     bind $top.list <B1-Motion> {break}
7142     bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
7143     set reflist {}
7144     refill_reflist
7147 proc sel_reflist {w x y} {
7148     global showrefstop reflist headids tagids otherrefids
7150     if {![winfo exists $showrefstop]} return
7151     set l [lindex [split [$w index "@$x,$y"] "."] 0]
7152     set ref [lindex $reflist [expr {$l-1}]]
7153     set n [lindex $ref 0]
7154     switch -- [lindex $ref 1] {
7155         "H" {selbyid $headids($n)}
7156         "T" {selbyid $tagids($n)}
7157         "o" {selbyid $otherrefids($n)}
7158     }
7159     $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
7162 proc unsel_reflist {} {
7163     global showrefstop
7165     if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7166     $showrefstop.list tag remove highlight 0.0 end
7169 proc reflistfilter_change {n1 n2 op} {
7170     global reflistfilter
7172     after cancel refill_reflist
7173     after 200 refill_reflist
7176 proc refill_reflist {} {
7177     global reflist reflistfilter showrefstop headids tagids otherrefids
7178     global curview commitinterest
7180     if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7181     set refs {}
7182     foreach n [array names headids] {
7183         if {[string match $reflistfilter $n]} {
7184             if {[commitinview $headids($n) $curview]} {
7185                 lappend refs [list $n H]
7186             } else {
7187                 set commitinterest($headids($n)) {run refill_reflist}
7188             }
7189         }
7190     }
7191     foreach n [array names tagids] {
7192         if {[string match $reflistfilter $n]} {
7193             if {[commitinview $tagids($n) $curview]} {
7194                 lappend refs [list $n T]
7195             } else {
7196                 set commitinterest($tagids($n)) {run refill_reflist}
7197             }
7198         }
7199     }
7200     foreach n [array names otherrefids] {
7201         if {[string match $reflistfilter $n]} {
7202             if {[commitinview $otherrefids($n) $curview]} {
7203                 lappend refs [list $n o]
7204             } else {
7205                 set commitinterest($otherrefids($n)) {run refill_reflist}
7206             }
7207         }
7208     }
7209     set refs [lsort -index 0 $refs]
7210     if {$refs eq $reflist} return
7212     # Update the contents of $showrefstop.list according to the
7213     # differences between $reflist (old) and $refs (new)
7214     $showrefstop.list conf -state normal
7215     $showrefstop.list insert end "\n"
7216     set i 0
7217     set j 0
7218     while {$i < [llength $reflist] || $j < [llength $refs]} {
7219         if {$i < [llength $reflist]} {
7220             if {$j < [llength $refs]} {
7221                 set cmp [string compare [lindex $reflist $i 0] \
7222                              [lindex $refs $j 0]]
7223                 if {$cmp == 0} {
7224                     set cmp [string compare [lindex $reflist $i 1] \
7225                                  [lindex $refs $j 1]]
7226                 }
7227             } else {
7228                 set cmp -1
7229             }
7230         } else {
7231             set cmp 1
7232         }
7233         switch -- $cmp {
7234             -1 {
7235                 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
7236                 incr i
7237             }
7238             0 {
7239                 incr i
7240                 incr j
7241             }
7242             1 {
7243                 set l [expr {$j + 1}]
7244                 $showrefstop.list image create $l.0 -align baseline \
7245                     -image reficon-[lindex $refs $j 1] -padx 2
7246                 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
7247                 incr j
7248             }
7249         }
7250     }
7251     set reflist $refs
7252     # delete last newline
7253     $showrefstop.list delete end-2c end-1c
7254     $showrefstop.list conf -state disabled
7257 # Stuff for finding nearby tags
7258 proc getallcommits {} {
7259     global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
7260     global idheads idtags idotherrefs allparents tagobjid
7262     if {![info exists allcommits]} {
7263         set nextarc 0
7264         set allcommits 0
7265         set seeds {}
7266         set allcwait 0
7267         set cachedarcs 0
7268         set allccache [file join [gitdir] "gitk.cache"]
7269         if {![catch {
7270             set f [open $allccache r]
7271             set allcwait 1
7272             getcache $f
7273         }]} return
7274     }
7276     if {$allcwait} {
7277         return
7278     }
7279     set cmd [list | git rev-list --parents]
7280     set allcupdate [expr {$seeds ne {}}]
7281     if {!$allcupdate} {
7282         set ids "--all"
7283     } else {
7284         set refs [concat [array names idheads] [array names idtags] \
7285                       [array names idotherrefs]]
7286         set ids {}
7287         set tagobjs {}
7288         foreach name [array names tagobjid] {
7289             lappend tagobjs $tagobjid($name)
7290         }
7291         foreach id [lsort -unique $refs] {
7292             if {![info exists allparents($id)] &&
7293                 [lsearch -exact $tagobjs $id] < 0} {
7294                 lappend ids $id
7295             }
7296         }
7297         if {$ids ne {}} {
7298             foreach id $seeds {
7299                 lappend ids "^$id"
7300             }
7301         }
7302     }
7303     if {$ids ne {}} {
7304         set fd [open [concat $cmd $ids] r]
7305         fconfigure $fd -blocking 0
7306         incr allcommits
7307         nowbusy allcommits
7308         filerun $fd [list getallclines $fd]
7309     } else {
7310         dispneartags 0
7311     }
7314 # Since most commits have 1 parent and 1 child, we group strings of
7315 # such commits into "arcs" joining branch/merge points (BMPs), which
7316 # are commits that either don't have 1 parent or don't have 1 child.
7318 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
7319 # arcout(id) - outgoing arcs for BMP
7320 # arcids(a) - list of IDs on arc including end but not start
7321 # arcstart(a) - BMP ID at start of arc
7322 # arcend(a) - BMP ID at end of arc
7323 # growing(a) - arc a is still growing
7324 # arctags(a) - IDs out of arcids (excluding end) that have tags
7325 # archeads(a) - IDs out of arcids (excluding end) that have heads
7326 # The start of an arc is at the descendent end, so "incoming" means
7327 # coming from descendents, and "outgoing" means going towards ancestors.
7329 proc getallclines {fd} {
7330     global allparents allchildren idtags idheads nextarc
7331     global arcnos arcids arctags arcout arcend arcstart archeads growing
7332     global seeds allcommits cachedarcs allcupdate
7333     
7334     set nid 0
7335     while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
7336         set id [lindex $line 0]
7337         if {[info exists allparents($id)]} {
7338             # seen it already
7339             continue
7340         }
7341         set cachedarcs 0
7342         set olds [lrange $line 1 end]
7343         set allparents($id) $olds
7344         if {![info exists allchildren($id)]} {
7345             set allchildren($id) {}
7346             set arcnos($id) {}
7347             lappend seeds $id
7348         } else {
7349             set a $arcnos($id)
7350             if {[llength $olds] == 1 && [llength $a] == 1} {
7351                 lappend arcids($a) $id
7352                 if {[info exists idtags($id)]} {
7353                     lappend arctags($a) $id
7354                 }
7355                 if {[info exists idheads($id)]} {
7356                     lappend archeads($a) $id
7357                 }
7358                 if {[info exists allparents($olds)]} {
7359                     # seen parent already
7360                     if {![info exists arcout($olds)]} {
7361                         splitarc $olds
7362                     }
7363                     lappend arcids($a) $olds
7364                     set arcend($a) $olds
7365                     unset growing($a)
7366                 }
7367                 lappend allchildren($olds) $id
7368                 lappend arcnos($olds) $a
7369                 continue
7370             }
7371         }
7372         foreach a $arcnos($id) {
7373             lappend arcids($a) $id
7374             set arcend($a) $id
7375             unset growing($a)
7376         }
7378         set ao {}
7379         foreach p $olds {
7380             lappend allchildren($p) $id
7381             set a [incr nextarc]
7382             set arcstart($a) $id
7383             set archeads($a) {}
7384             set arctags($a) {}
7385             set archeads($a) {}
7386             set arcids($a) {}
7387             lappend ao $a
7388             set growing($a) 1
7389             if {[info exists allparents($p)]} {
7390                 # seen it already, may need to make a new branch
7391                 if {![info exists arcout($p)]} {
7392                     splitarc $p
7393                 }
7394                 lappend arcids($a) $p
7395                 set arcend($a) $p
7396                 unset growing($a)
7397             }
7398             lappend arcnos($p) $a
7399         }
7400         set arcout($id) $ao
7401     }
7402     if {$nid > 0} {
7403         global cached_dheads cached_dtags cached_atags
7404         catch {unset cached_dheads}
7405         catch {unset cached_dtags}
7406         catch {unset cached_atags}
7407     }
7408     if {![eof $fd]} {
7409         return [expr {$nid >= 1000? 2: 1}]
7410     }
7411     set cacheok 1
7412     if {[catch {
7413         fconfigure $fd -blocking 1
7414         close $fd
7415     } err]} {
7416         # got an error reading the list of commits
7417         # if we were updating, try rereading the whole thing again
7418         if {$allcupdate} {
7419             incr allcommits -1
7420             dropcache $err
7421             return
7422         }
7423         error_popup "Error reading commit topology information;\
7424                 branch and preceding/following tag information\
7425                 will be incomplete.\n($err)"
7426         set cacheok 0
7427     }
7428     if {[incr allcommits -1] == 0} {
7429         notbusy allcommits
7430         if {$cacheok} {
7431             run savecache
7432         }
7433     }
7434     dispneartags 0
7435     return 0
7438 proc recalcarc {a} {
7439     global arctags archeads arcids idtags idheads
7441     set at {}
7442     set ah {}
7443     foreach id [lrange $arcids($a) 0 end-1] {
7444         if {[info exists idtags($id)]} {
7445             lappend at $id
7446         }
7447         if {[info exists idheads($id)]} {
7448             lappend ah $id
7449         }
7450     }
7451     set arctags($a) $at
7452     set archeads($a) $ah
7455 proc splitarc {p} {
7456     global arcnos arcids nextarc arctags archeads idtags idheads
7457     global arcstart arcend arcout allparents growing
7459     set a $arcnos($p)
7460     if {[llength $a] != 1} {
7461         puts "oops splitarc called but [llength $a] arcs already"
7462         return
7463     }
7464     set a [lindex $a 0]
7465     set i [lsearch -exact $arcids($a) $p]
7466     if {$i < 0} {
7467         puts "oops splitarc $p not in arc $a"
7468         return
7469     }
7470     set na [incr nextarc]
7471     if {[info exists arcend($a)]} {
7472         set arcend($na) $arcend($a)
7473     } else {
7474         set l [lindex $allparents([lindex $arcids($a) end]) 0]
7475         set j [lsearch -exact $arcnos($l) $a]
7476         set arcnos($l) [lreplace $arcnos($l) $j $j $na]
7477     }
7478     set tail [lrange $arcids($a) [expr {$i+1}] end]
7479     set arcids($a) [lrange $arcids($a) 0 $i]
7480     set arcend($a) $p
7481     set arcstart($na) $p
7482     set arcout($p) $na
7483     set arcids($na) $tail
7484     if {[info exists growing($a)]} {
7485         set growing($na) 1
7486         unset growing($a)
7487     }
7489     foreach id $tail {
7490         if {[llength $arcnos($id)] == 1} {
7491             set arcnos($id) $na
7492         } else {
7493             set j [lsearch -exact $arcnos($id) $a]
7494             set arcnos($id) [lreplace $arcnos($id) $j $j $na]
7495         }
7496     }
7498     # reconstruct tags and heads lists
7499     if {$arctags($a) ne {} || $archeads($a) ne {}} {
7500         recalcarc $a
7501         recalcarc $na
7502     } else {
7503         set arctags($na) {}
7504         set archeads($na) {}
7505     }
7508 # Update things for a new commit added that is a child of one
7509 # existing commit.  Used when cherry-picking.
7510 proc addnewchild {id p} {
7511     global allparents allchildren idtags nextarc
7512     global arcnos arcids arctags arcout arcend arcstart archeads growing
7513     global seeds allcommits
7515     if {![info exists allcommits] || ![info exists arcnos($p)]} return
7516     set allparents($id) [list $p]
7517     set allchildren($id) {}
7518     set arcnos($id) {}
7519     lappend seeds $id
7520     lappend allchildren($p) $id
7521     set a [incr nextarc]
7522     set arcstart($a) $id
7523     set archeads($a) {}
7524     set arctags($a) {}
7525     set arcids($a) [list $p]
7526     set arcend($a) $p
7527     if {![info exists arcout($p)]} {
7528         splitarc $p
7529     }
7530     lappend arcnos($p) $a
7531     set arcout($id) [list $a]
7534 # This implements a cache for the topology information.
7535 # The cache saves, for each arc, the start and end of the arc,
7536 # the ids on the arc, and the outgoing arcs from the end.
7537 proc readcache {f} {
7538     global arcnos arcids arcout arcstart arcend arctags archeads nextarc
7539     global idtags idheads allparents cachedarcs possible_seeds seeds growing
7540     global allcwait
7542     set a $nextarc
7543     set lim $cachedarcs
7544     if {$lim - $a > 500} {
7545         set lim [expr {$a + 500}]
7546     }
7547     if {[catch {
7548         if {$a == $lim} {
7549             # finish reading the cache and setting up arctags, etc.
7550             set line [gets $f]
7551             if {$line ne "1"} {error "bad final version"}
7552             close $f
7553             foreach id [array names idtags] {
7554                 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
7555                     [llength $allparents($id)] == 1} {
7556                     set a [lindex $arcnos($id) 0]
7557                     if {$arctags($a) eq {}} {
7558                         recalcarc $a
7559                     }
7560                 }
7561             }
7562             foreach id [array names idheads] {
7563                 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
7564                     [llength $allparents($id)] == 1} {
7565                     set a [lindex $arcnos($id) 0]
7566                     if {$archeads($a) eq {}} {
7567                         recalcarc $a
7568                     }
7569                 }
7570             }
7571             foreach id [lsort -unique $possible_seeds] {
7572                 if {$arcnos($id) eq {}} {
7573                     lappend seeds $id
7574                 }
7575             }
7576             set allcwait 0
7577         } else {
7578             while {[incr a] <= $lim} {
7579                 set line [gets $f]
7580                 if {[llength $line] != 3} {error "bad line"}
7581                 set s [lindex $line 0]
7582                 set arcstart($a) $s
7583                 lappend arcout($s) $a
7584                 if {![info exists arcnos($s)]} {
7585                     lappend possible_seeds $s
7586                     set arcnos($s) {}
7587                 }
7588                 set e [lindex $line 1]
7589                 if {$e eq {}} {
7590                     set growing($a) 1
7591                 } else {
7592                     set arcend($a) $e
7593                     if {![info exists arcout($e)]} {
7594                         set arcout($e) {}
7595                     }
7596                 }
7597                 set arcids($a) [lindex $line 2]
7598                 foreach id $arcids($a) {
7599                     lappend allparents($s) $id
7600                     set s $id
7601                     lappend arcnos($id) $a
7602                 }
7603                 if {![info exists allparents($s)]} {
7604                     set allparents($s) {}
7605                 }
7606                 set arctags($a) {}
7607                 set archeads($a) {}
7608             }
7609             set nextarc [expr {$a - 1}]
7610         }
7611     } err]} {
7612         dropcache $err
7613         return 0
7614     }
7615     if {!$allcwait} {
7616         getallcommits
7617     }
7618     return $allcwait
7621 proc getcache {f} {
7622     global nextarc cachedarcs possible_seeds
7624     if {[catch {
7625         set line [gets $f]
7626         if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
7627         # make sure it's an integer
7628         set cachedarcs [expr {int([lindex $line 1])}]
7629         if {$cachedarcs < 0} {error "bad number of arcs"}
7630         set nextarc 0
7631         set possible_seeds {}
7632         run readcache $f
7633     } err]} {
7634         dropcache $err
7635     }
7636     return 0
7639 proc dropcache {err} {
7640     global allcwait nextarc cachedarcs seeds
7642     #puts "dropping cache ($err)"
7643     foreach v {arcnos arcout arcids arcstart arcend growing \
7644                    arctags archeads allparents allchildren} {
7645         global $v
7646         catch {unset $v}
7647     }
7648     set allcwait 0
7649     set nextarc 0
7650     set cachedarcs 0
7651     set seeds {}
7652     getallcommits
7655 proc writecache {f} {
7656     global cachearc cachedarcs allccache
7657     global arcstart arcend arcnos arcids arcout
7659     set a $cachearc
7660     set lim $cachedarcs
7661     if {$lim - $a > 1000} {
7662         set lim [expr {$a + 1000}]
7663     }
7664     if {[catch {
7665         while {[incr a] <= $lim} {
7666             if {[info exists arcend($a)]} {
7667                 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
7668             } else {
7669                 puts $f [list $arcstart($a) {} $arcids($a)]
7670             }
7671         }
7672     } err]} {
7673         catch {close $f}
7674         catch {file delete $allccache}
7675         #puts "writing cache failed ($err)"
7676         return 0
7677     }
7678     set cachearc [expr {$a - 1}]
7679     if {$a > $cachedarcs} {
7680         puts $f "1"
7681         close $f
7682         return 0
7683     }
7684     return 1
7687 proc savecache {} {
7688     global nextarc cachedarcs cachearc allccache
7690     if {$nextarc == $cachedarcs} return
7691     set cachearc 0
7692     set cachedarcs $nextarc
7693     catch {
7694         set f [open $allccache w]
7695         puts $f [list 1 $cachedarcs]
7696         run writecache $f
7697     }
7700 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
7701 # or 0 if neither is true.
7702 proc anc_or_desc {a b} {
7703     global arcout arcstart arcend arcnos cached_isanc
7705     if {$arcnos($a) eq $arcnos($b)} {
7706         # Both are on the same arc(s); either both are the same BMP,
7707         # or if one is not a BMP, the other is also not a BMP or is
7708         # the BMP at end of the arc (and it only has 1 incoming arc).
7709         # Or both can be BMPs with no incoming arcs.
7710         if {$a eq $b || $arcnos($a) eq {}} {
7711             return 0
7712         }
7713         # assert {[llength $arcnos($a)] == 1}
7714         set arc [lindex $arcnos($a) 0]
7715         set i [lsearch -exact $arcids($arc) $a]
7716         set j [lsearch -exact $arcids($arc) $b]
7717         if {$i < 0 || $i > $j} {
7718             return 1
7719         } else {
7720             return -1
7721         }
7722     }
7724     if {![info exists arcout($a)]} {
7725         set arc [lindex $arcnos($a) 0]
7726         if {[info exists arcend($arc)]} {
7727             set aend $arcend($arc)
7728         } else {
7729             set aend {}
7730         }
7731         set a $arcstart($arc)
7732     } else {
7733         set aend $a
7734     }
7735     if {![info exists arcout($b)]} {
7736         set arc [lindex $arcnos($b) 0]
7737         if {[info exists arcend($arc)]} {
7738             set bend $arcend($arc)
7739         } else {
7740             set bend {}
7741         }
7742         set b $arcstart($arc)
7743     } else {
7744         set bend $b
7745     }
7746     if {$a eq $bend} {
7747         return 1
7748     }
7749     if {$b eq $aend} {
7750         return -1
7751     }
7752     if {[info exists cached_isanc($a,$bend)]} {
7753         if {$cached_isanc($a,$bend)} {
7754             return 1
7755         }
7756     }
7757     if {[info exists cached_isanc($b,$aend)]} {
7758         if {$cached_isanc($b,$aend)} {
7759             return -1
7760         }
7761         if {[info exists cached_isanc($a,$bend)]} {
7762             return 0
7763         }
7764     }
7766     set todo [list $a $b]
7767     set anc($a) a
7768     set anc($b) b
7769     for {set i 0} {$i < [llength $todo]} {incr i} {
7770         set x [lindex $todo $i]
7771         if {$anc($x) eq {}} {
7772             continue
7773         }
7774         foreach arc $arcnos($x) {
7775             set xd $arcstart($arc)
7776             if {$xd eq $bend} {
7777                 set cached_isanc($a,$bend) 1
7778                 set cached_isanc($b,$aend) 0
7779                 return 1
7780             } elseif {$xd eq $aend} {
7781                 set cached_isanc($b,$aend) 1
7782                 set cached_isanc($a,$bend) 0
7783                 return -1
7784             }
7785             if {![info exists anc($xd)]} {
7786                 set anc($xd) $anc($x)
7787                 lappend todo $xd
7788             } elseif {$anc($xd) ne $anc($x)} {
7789                 set anc($xd) {}
7790             }
7791         }
7792     }
7793     set cached_isanc($a,$bend) 0
7794     set cached_isanc($b,$aend) 0
7795     return 0
7798 # This identifies whether $desc has an ancestor that is
7799 # a growing tip of the graph and which is not an ancestor of $anc
7800 # and returns 0 if so and 1 if not.
7801 # If we subsequently discover a tag on such a growing tip, and that
7802 # turns out to be a descendent of $anc (which it could, since we
7803 # don't necessarily see children before parents), then $desc
7804 # isn't a good choice to display as a descendent tag of
7805 # $anc (since it is the descendent of another tag which is
7806 # a descendent of $anc).  Similarly, $anc isn't a good choice to
7807 # display as a ancestor tag of $desc.
7809 proc is_certain {desc anc} {
7810     global arcnos arcout arcstart arcend growing problems
7812     set certain {}
7813     if {[llength $arcnos($anc)] == 1} {
7814         # tags on the same arc are certain
7815         if {$arcnos($desc) eq $arcnos($anc)} {
7816             return 1
7817         }
7818         if {![info exists arcout($anc)]} {
7819             # if $anc is partway along an arc, use the start of the arc instead
7820             set a [lindex $arcnos($anc) 0]
7821             set anc $arcstart($a)
7822         }
7823     }
7824     if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
7825         set x $desc
7826     } else {
7827         set a [lindex $arcnos($desc) 0]
7828         set x $arcend($a)
7829     }
7830     if {$x == $anc} {
7831         return 1
7832     }
7833     set anclist [list $x]
7834     set dl($x) 1
7835     set nnh 1
7836     set ngrowanc 0
7837     for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
7838         set x [lindex $anclist $i]
7839         if {$dl($x)} {
7840             incr nnh -1
7841         }
7842         set done($x) 1
7843         foreach a $arcout($x) {
7844             if {[info exists growing($a)]} {
7845                 if {![info exists growanc($x)] && $dl($x)} {
7846                     set growanc($x) 1
7847                     incr ngrowanc
7848                 }
7849             } else {
7850                 set y $arcend($a)
7851                 if {[info exists dl($y)]} {
7852                     if {$dl($y)} {
7853                         if {!$dl($x)} {
7854                             set dl($y) 0
7855                             if {![info exists done($y)]} {
7856                                 incr nnh -1
7857                             }
7858                             if {[info exists growanc($x)]} {
7859                                 incr ngrowanc -1
7860                             }
7861                             set xl [list $y]
7862                             for {set k 0} {$k < [llength $xl]} {incr k} {
7863                                 set z [lindex $xl $k]
7864                                 foreach c $arcout($z) {
7865                                     if {[info exists arcend($c)]} {
7866                                         set v $arcend($c)
7867                                         if {[info exists dl($v)] && $dl($v)} {
7868                                             set dl($v) 0
7869                                             if {![info exists done($v)]} {
7870                                                 incr nnh -1
7871                                             }
7872                                             if {[info exists growanc($v)]} {
7873                                                 incr ngrowanc -1
7874                                             }
7875                                             lappend xl $v
7876                                         }
7877                                     }
7878                                 }
7879                             }
7880                         }
7881                     }
7882                 } elseif {$y eq $anc || !$dl($x)} {
7883                     set dl($y) 0
7884                     lappend anclist $y
7885                 } else {
7886                     set dl($y) 1
7887                     lappend anclist $y
7888                     incr nnh
7889                 }
7890             }
7891         }
7892     }
7893     foreach x [array names growanc] {
7894         if {$dl($x)} {
7895             return 0
7896         }
7897         return 0
7898     }
7899     return 1
7902 proc validate_arctags {a} {
7903     global arctags idtags
7905     set i -1
7906     set na $arctags($a)
7907     foreach id $arctags($a) {
7908         incr i
7909         if {![info exists idtags($id)]} {
7910             set na [lreplace $na $i $i]
7911             incr i -1
7912         }
7913     }
7914     set arctags($a) $na
7917 proc validate_archeads {a} {
7918     global archeads idheads
7920     set i -1
7921     set na $archeads($a)
7922     foreach id $archeads($a) {
7923         incr i
7924         if {![info exists idheads($id)]} {
7925             set na [lreplace $na $i $i]
7926             incr i -1
7927         }
7928     }
7929     set archeads($a) $na
7932 # Return the list of IDs that have tags that are descendents of id,
7933 # ignoring IDs that are descendents of IDs already reported.
7934 proc desctags {id} {
7935     global arcnos arcstart arcids arctags idtags allparents
7936     global growing cached_dtags
7938     if {![info exists allparents($id)]} {
7939         return {}
7940     }
7941     set t1 [clock clicks -milliseconds]
7942     set argid $id
7943     if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
7944         # part-way along an arc; check that arc first
7945         set a [lindex $arcnos($id) 0]
7946         if {$arctags($a) ne {}} {
7947             validate_arctags $a
7948             set i [lsearch -exact $arcids($a) $id]
7949             set tid {}
7950             foreach t $arctags($a) {
7951                 set j [lsearch -exact $arcids($a) $t]
7952                 if {$j >= $i} break
7953                 set tid $t
7954             }
7955             if {$tid ne {}} {
7956                 return $tid
7957             }
7958         }
7959         set id $arcstart($a)
7960         if {[info exists idtags($id)]} {
7961             return $id
7962         }
7963     }
7964     if {[info exists cached_dtags($id)]} {
7965         return $cached_dtags($id)
7966     }
7968     set origid $id
7969     set todo [list $id]
7970     set queued($id) 1
7971     set nc 1
7972     for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
7973         set id [lindex $todo $i]
7974         set done($id) 1
7975         set ta [info exists hastaggedancestor($id)]
7976         if {!$ta} {
7977             incr nc -1
7978         }
7979         # ignore tags on starting node
7980         if {!$ta && $i > 0} {
7981             if {[info exists idtags($id)]} {
7982                 set tagloc($id) $id
7983                 set ta 1
7984             } elseif {[info exists cached_dtags($id)]} {
7985                 set tagloc($id) $cached_dtags($id)
7986                 set ta 1
7987             }
7988         }
7989         foreach a $arcnos($id) {
7990             set d $arcstart($a)
7991             if {!$ta && $arctags($a) ne {}} {
7992                 validate_arctags $a
7993                 if {$arctags($a) ne {}} {
7994                     lappend tagloc($id) [lindex $arctags($a) end]
7995                 }
7996             }
7997             if {$ta || $arctags($a) ne {}} {
7998                 set tomark [list $d]
7999                 for {set j 0} {$j < [llength $tomark]} {incr j} {
8000                     set dd [lindex $tomark $j]
8001                     if {![info exists hastaggedancestor($dd)]} {
8002                         if {[info exists done($dd)]} {
8003                             foreach b $arcnos($dd) {
8004                                 lappend tomark $arcstart($b)
8005                             }
8006                             if {[info exists tagloc($dd)]} {
8007                                 unset tagloc($dd)
8008                             }
8009                         } elseif {[info exists queued($dd)]} {
8010                             incr nc -1
8011                         }
8012                         set hastaggedancestor($dd) 1
8013                     }
8014                 }
8015             }
8016             if {![info exists queued($d)]} {
8017                 lappend todo $d
8018                 set queued($d) 1
8019                 if {![info exists hastaggedancestor($d)]} {
8020                     incr nc
8021                 }
8022             }
8023         }
8024     }
8025     set tags {}
8026     foreach id [array names tagloc] {
8027         if {![info exists hastaggedancestor($id)]} {
8028             foreach t $tagloc($id) {
8029                 if {[lsearch -exact $tags $t] < 0} {
8030                     lappend tags $t
8031                 }
8032             }
8033         }
8034     }
8035     set t2 [clock clicks -milliseconds]
8036     set loopix $i
8038     # remove tags that are descendents of other tags
8039     for {set i 0} {$i < [llength $tags]} {incr i} {
8040         set a [lindex $tags $i]
8041         for {set j 0} {$j < $i} {incr j} {
8042             set b [lindex $tags $j]
8043             set r [anc_or_desc $a $b]
8044             if {$r == 1} {
8045                 set tags [lreplace $tags $j $j]
8046                 incr j -1
8047                 incr i -1
8048             } elseif {$r == -1} {
8049                 set tags [lreplace $tags $i $i]
8050                 incr i -1
8051                 break
8052             }
8053         }
8054     }
8056     if {[array names growing] ne {}} {
8057         # graph isn't finished, need to check if any tag could get
8058         # eclipsed by another tag coming later.  Simply ignore any
8059         # tags that could later get eclipsed.
8060         set ctags {}
8061         foreach t $tags {
8062             if {[is_certain $t $origid]} {
8063                 lappend ctags $t
8064             }
8065         }
8066         if {$tags eq $ctags} {
8067             set cached_dtags($origid) $tags
8068         } else {
8069             set tags $ctags
8070         }
8071     } else {
8072         set cached_dtags($origid) $tags
8073     }
8074     set t3 [clock clicks -milliseconds]
8075     if {0 && $t3 - $t1 >= 100} {
8076         puts "iterating descendents ($loopix/[llength $todo] nodes) took\
8077             [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8078     }
8079     return $tags
8082 proc anctags {id} {
8083     global arcnos arcids arcout arcend arctags idtags allparents
8084     global growing cached_atags
8086     if {![info exists allparents($id)]} {
8087         return {}
8088     }
8089     set t1 [clock clicks -milliseconds]
8090     set argid $id
8091     if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8092         # part-way along an arc; check that arc first
8093         set a [lindex $arcnos($id) 0]
8094         if {$arctags($a) ne {}} {
8095             validate_arctags $a
8096             set i [lsearch -exact $arcids($a) $id]
8097             foreach t $arctags($a) {
8098                 set j [lsearch -exact $arcids($a) $t]
8099                 if {$j > $i} {
8100                     return $t
8101                 }
8102             }
8103         }
8104         if {![info exists arcend($a)]} {
8105             return {}
8106         }
8107         set id $arcend($a)
8108         if {[info exists idtags($id)]} {
8109             return $id
8110         }
8111     }
8112     if {[info exists cached_atags($id)]} {
8113         return $cached_atags($id)
8114     }
8116     set origid $id
8117     set todo [list $id]
8118     set queued($id) 1
8119     set taglist {}
8120     set nc 1
8121     for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8122         set id [lindex $todo $i]
8123         set done($id) 1
8124         set td [info exists hastaggeddescendent($id)]
8125         if {!$td} {
8126             incr nc -1
8127         }
8128         # ignore tags on starting node
8129         if {!$td && $i > 0} {
8130             if {[info exists idtags($id)]} {
8131                 set tagloc($id) $id
8132                 set td 1
8133             } elseif {[info exists cached_atags($id)]} {
8134                 set tagloc($id) $cached_atags($id)
8135                 set td 1
8136             }
8137         }
8138         foreach a $arcout($id) {
8139             if {!$td && $arctags($a) ne {}} {
8140                 validate_arctags $a
8141                 if {$arctags($a) ne {}} {
8142                     lappend tagloc($id) [lindex $arctags($a) 0]
8143                 }
8144             }
8145             if {![info exists arcend($a)]} continue
8146             set d $arcend($a)
8147             if {$td || $arctags($a) ne {}} {
8148                 set tomark [list $d]
8149                 for {set j 0} {$j < [llength $tomark]} {incr j} {
8150                     set dd [lindex $tomark $j]
8151                     if {![info exists hastaggeddescendent($dd)]} {
8152                         if {[info exists done($dd)]} {
8153                             foreach b $arcout($dd) {
8154                                 if {[info exists arcend($b)]} {
8155                                     lappend tomark $arcend($b)
8156                                 }
8157                             }
8158                             if {[info exists tagloc($dd)]} {
8159                                 unset tagloc($dd)
8160                             }
8161                         } elseif {[info exists queued($dd)]} {
8162                             incr nc -1
8163                         }
8164                         set hastaggeddescendent($dd) 1
8165                     }
8166                 }
8167             }
8168             if {![info exists queued($d)]} {
8169                 lappend todo $d
8170                 set queued($d) 1
8171                 if {![info exists hastaggeddescendent($d)]} {
8172                     incr nc
8173                 }
8174             }
8175         }
8176     }
8177     set t2 [clock clicks -milliseconds]
8178     set loopix $i
8179     set tags {}
8180     foreach id [array names tagloc] {
8181         if {![info exists hastaggeddescendent($id)]} {
8182             foreach t $tagloc($id) {
8183                 if {[lsearch -exact $tags $t] < 0} {
8184                     lappend tags $t
8185                 }
8186             }
8187         }
8188     }
8190     # remove tags that are ancestors of other tags
8191     for {set i 0} {$i < [llength $tags]} {incr i} {
8192         set a [lindex $tags $i]
8193         for {set j 0} {$j < $i} {incr j} {
8194             set b [lindex $tags $j]
8195             set r [anc_or_desc $a $b]
8196             if {$r == -1} {
8197                 set tags [lreplace $tags $j $j]
8198                 incr j -1
8199                 incr i -1
8200             } elseif {$r == 1} {
8201                 set tags [lreplace $tags $i $i]
8202                 incr i -1
8203                 break
8204             }
8205         }
8206     }
8208     if {[array names growing] ne {}} {
8209         # graph isn't finished, need to check if any tag could get
8210         # eclipsed by another tag coming later.  Simply ignore any
8211         # tags that could later get eclipsed.
8212         set ctags {}
8213         foreach t $tags {
8214             if {[is_certain $origid $t]} {
8215                 lappend ctags $t
8216             }
8217         }
8218         if {$tags eq $ctags} {
8219             set cached_atags($origid) $tags
8220         } else {
8221             set tags $ctags
8222         }
8223     } else {
8224         set cached_atags($origid) $tags
8225     }
8226     set t3 [clock clicks -milliseconds]
8227     if {0 && $t3 - $t1 >= 100} {
8228         puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
8229             [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8230     }
8231     return $tags
8234 # Return the list of IDs that have heads that are descendents of id,
8235 # including id itself if it has a head.
8236 proc descheads {id} {
8237     global arcnos arcstart arcids archeads idheads cached_dheads
8238     global allparents
8240     if {![info exists allparents($id)]} {
8241         return {}
8242     }
8243     set aret {}
8244     if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8245         # part-way along an arc; check it first
8246         set a [lindex $arcnos($id) 0]
8247         if {$archeads($a) ne {}} {
8248             validate_archeads $a
8249             set i [lsearch -exact $arcids($a) $id]
8250             foreach t $archeads($a) {
8251                 set j [lsearch -exact $arcids($a) $t]
8252                 if {$j > $i} break
8253                 lappend aret $t
8254             }
8255         }
8256         set id $arcstart($a)
8257     }
8258     set origid $id
8259     set todo [list $id]
8260     set seen($id) 1
8261     set ret {}
8262     for {set i 0} {$i < [llength $todo]} {incr i} {
8263         set id [lindex $todo $i]
8264         if {[info exists cached_dheads($id)]} {
8265             set ret [concat $ret $cached_dheads($id)]
8266         } else {
8267             if {[info exists idheads($id)]} {
8268                 lappend ret $id
8269             }
8270             foreach a $arcnos($id) {
8271                 if {$archeads($a) ne {}} {
8272                     validate_archeads $a
8273                     if {$archeads($a) ne {}} {
8274                         set ret [concat $ret $archeads($a)]
8275                     }
8276                 }
8277                 set d $arcstart($a)
8278                 if {![info exists seen($d)]} {
8279                     lappend todo $d
8280                     set seen($d) 1
8281                 }
8282             }
8283         }
8284     }
8285     set ret [lsort -unique $ret]
8286     set cached_dheads($origid) $ret
8287     return [concat $ret $aret]
8290 proc addedtag {id} {
8291     global arcnos arcout cached_dtags cached_atags
8293     if {![info exists arcnos($id)]} return
8294     if {![info exists arcout($id)]} {
8295         recalcarc [lindex $arcnos($id) 0]
8296     }
8297     catch {unset cached_dtags}
8298     catch {unset cached_atags}
8301 proc addedhead {hid head} {
8302     global arcnos arcout cached_dheads
8304     if {![info exists arcnos($hid)]} return
8305     if {![info exists arcout($hid)]} {
8306         recalcarc [lindex $arcnos($hid) 0]
8307     }
8308     catch {unset cached_dheads}
8311 proc removedhead {hid head} {
8312     global cached_dheads
8314     catch {unset cached_dheads}
8317 proc movedhead {hid head} {
8318     global arcnos arcout cached_dheads
8320     if {![info exists arcnos($hid)]} return
8321     if {![info exists arcout($hid)]} {
8322         recalcarc [lindex $arcnos($hid) 0]
8323     }
8324     catch {unset cached_dheads}
8327 proc changedrefs {} {
8328     global cached_dheads cached_dtags cached_atags
8329     global arctags archeads arcnos arcout idheads idtags
8331     foreach id [concat [array names idheads] [array names idtags]] {
8332         if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
8333             set a [lindex $arcnos($id) 0]
8334             if {![info exists donearc($a)]} {
8335                 recalcarc $a
8336                 set donearc($a) 1
8337             }
8338         }
8339     }
8340     catch {unset cached_dtags}
8341     catch {unset cached_atags}
8342     catch {unset cached_dheads}
8345 proc rereadrefs {} {
8346     global idtags idheads idotherrefs mainhead
8348     set refids [concat [array names idtags] \
8349                     [array names idheads] [array names idotherrefs]]
8350     foreach id $refids {
8351         if {![info exists ref($id)]} {
8352             set ref($id) [listrefs $id]
8353         }
8354     }
8355     set oldmainhead $mainhead
8356     readrefs
8357     changedrefs
8358     set refids [lsort -unique [concat $refids [array names idtags] \
8359                         [array names idheads] [array names idotherrefs]]]
8360     foreach id $refids {
8361         set v [listrefs $id]
8362         if {![info exists ref($id)] || $ref($id) != $v ||
8363             ($id eq $oldmainhead && $id ne $mainhead) ||
8364             ($id eq $mainhead && $id ne $oldmainhead)} {
8365             redrawtags $id
8366         }
8367     }
8368     run refill_reflist
8371 proc listrefs {id} {
8372     global idtags idheads idotherrefs
8374     set x {}
8375     if {[info exists idtags($id)]} {
8376         set x $idtags($id)
8377     }
8378     set y {}
8379     if {[info exists idheads($id)]} {
8380         set y $idheads($id)
8381     }
8382     set z {}
8383     if {[info exists idotherrefs($id)]} {
8384         set z $idotherrefs($id)
8385     }
8386     return [list $x $y $z]
8389 proc showtag {tag isnew} {
8390     global ctext tagcontents tagids linknum tagobjid
8392     if {$isnew} {
8393         addtohistory [list showtag $tag 0]
8394     }
8395     $ctext conf -state normal
8396     clear_ctext
8397     settabs 0
8398     set linknum 0
8399     if {![info exists tagcontents($tag)]} {
8400         catch {
8401             set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
8402         }
8403     }
8404     if {[info exists tagcontents($tag)]} {
8405         set text $tagcontents($tag)
8406     } else {
8407         set text "Tag: $tag\nId:  $tagids($tag)"
8408     }
8409     appendwithlinks $text {}
8410     $ctext conf -state disabled
8411     init_flist {}
8414 proc doquit {} {
8415     global stopped
8416     set stopped 100
8417     savestuff .
8418     destroy .
8421 proc mkfontdisp {font top which} {
8422     global fontattr fontpref $font
8424     set fontpref($font) [set $font]
8425     button $top.${font}but -text $which -font optionfont \
8426         -command [list choosefont $font $which]
8427     label $top.$font -relief flat -font $font \
8428         -text $fontattr($font,family) -justify left
8429     grid x $top.${font}but $top.$font -sticky w
8432 proc choosefont {font which} {
8433     global fontparam fontlist fonttop fontattr
8435     set fontparam(which) $which
8436     set fontparam(font) $font
8437     set fontparam(family) [font actual $font -family]
8438     set fontparam(size) $fontattr($font,size)
8439     set fontparam(weight) $fontattr($font,weight)
8440     set fontparam(slant) $fontattr($font,slant)
8441     set top .gitkfont
8442     set fonttop $top
8443     if {![winfo exists $top]} {
8444         font create sample
8445         eval font config sample [font actual $font]
8446         toplevel $top
8447         wm title $top "Gitk font chooser"
8448         label $top.l -textvariable fontparam(which) -font uifont
8449         pack $top.l -side top
8450         set fontlist [lsort [font families]]
8451         frame $top.f
8452         listbox $top.f.fam -listvariable fontlist \
8453             -yscrollcommand [list $top.f.sb set]
8454         bind $top.f.fam <<ListboxSelect>> selfontfam
8455         scrollbar $top.f.sb -command [list $top.f.fam yview]
8456         pack $top.f.sb -side right -fill y
8457         pack $top.f.fam -side left -fill both -expand 1
8458         pack $top.f -side top -fill both -expand 1
8459         frame $top.g
8460         spinbox $top.g.size -from 4 -to 40 -width 4 \
8461             -textvariable fontparam(size) \
8462             -validatecommand {string is integer -strict %s}
8463         checkbutton $top.g.bold -padx 5 \
8464             -font {{Times New Roman} 12 bold} -text "B" -indicatoron 0 \
8465             -variable fontparam(weight) -onvalue bold -offvalue normal
8466         checkbutton $top.g.ital -padx 5 \
8467             -font {{Times New Roman} 12 italic} -text "I" -indicatoron 0  \
8468             -variable fontparam(slant) -onvalue italic -offvalue roman
8469         pack $top.g.size $top.g.bold $top.g.ital -side left
8470         pack $top.g -side top
8471         canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
8472             -background white
8473         $top.c create text 100 25 -anchor center -text $which -font sample \
8474             -fill black -tags text
8475         bind $top.c <Configure> [list centertext $top.c]
8476         pack $top.c -side top -fill x
8477         frame $top.buts
8478         button $top.buts.ok -text "OK" -command fontok -default active \
8479             -font uifont
8480         button $top.buts.can -text "Cancel" -command fontcan -default normal \
8481             -font uifont
8482         grid $top.buts.ok $top.buts.can
8483         grid columnconfigure $top.buts 0 -weight 1 -uniform a
8484         grid columnconfigure $top.buts 1 -weight 1 -uniform a
8485         pack $top.buts -side bottom -fill x
8486         trace add variable fontparam write chg_fontparam
8487     } else {
8488         raise $top
8489         $top.c itemconf text -text $which
8490     }
8491     set i [lsearch -exact $fontlist $fontparam(family)]
8492     if {$i >= 0} {
8493         $top.f.fam selection set $i
8494         $top.f.fam see $i
8495     }
8498 proc centertext {w} {
8499     $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
8502 proc fontok {} {
8503     global fontparam fontpref prefstop
8505     set f $fontparam(font)
8506     set fontpref($f) [list $fontparam(family) $fontparam(size)]
8507     if {$fontparam(weight) eq "bold"} {
8508         lappend fontpref($f) "bold"
8509     }
8510     if {$fontparam(slant) eq "italic"} {
8511         lappend fontpref($f) "italic"
8512     }
8513     set w $prefstop.$f
8514     $w conf -text $fontparam(family) -font $fontpref($f)
8515         
8516     fontcan
8519 proc fontcan {} {
8520     global fonttop fontparam
8522     if {[info exists fonttop]} {
8523         catch {destroy $fonttop}
8524         catch {font delete sample}
8525         unset fonttop
8526         unset fontparam
8527     }
8530 proc selfontfam {} {
8531     global fonttop fontparam
8533     set i [$fonttop.f.fam curselection]
8534     if {$i ne {}} {
8535         set fontparam(family) [$fonttop.f.fam get $i]
8536     }
8539 proc chg_fontparam {v sub op} {
8540     global fontparam
8542     font config sample -$sub $fontparam($sub)
8545 proc doprefs {} {
8546     global maxwidth maxgraphpct
8547     global oldprefs prefstop showneartags showlocalchanges
8548     global bgcolor fgcolor ctext diffcolors selectbgcolor
8549     global uifont tabstop limitdiffs
8551     set top .gitkprefs
8552     set prefstop $top
8553     if {[winfo exists $top]} {
8554         raise $top
8555         return
8556     }
8557     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
8558                    limitdiffs tabstop} {
8559         set oldprefs($v) [set $v]
8560     }
8561     toplevel $top
8562     wm title $top "Gitk preferences"
8563     label $top.ldisp -text "Commit list display options"
8564     $top.ldisp configure -font uifont
8565     grid $top.ldisp - -sticky w -pady 10
8566     label $top.spacer -text " "
8567     label $top.maxwidthl -text "Maximum graph width (lines)" \
8568         -font optionfont
8569     spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
8570     grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
8571     label $top.maxpctl -text "Maximum graph width (% of pane)" \
8572         -font optionfont
8573     spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
8574     grid x $top.maxpctl $top.maxpct -sticky w
8575     frame $top.showlocal
8576     label $top.showlocal.l -text "Show local changes" -font optionfont
8577     checkbutton $top.showlocal.b -variable showlocalchanges
8578     pack $top.showlocal.b $top.showlocal.l -side left
8579     grid x $top.showlocal -sticky w
8581     label $top.ddisp -text "Diff display options"
8582     $top.ddisp configure -font uifont
8583     grid $top.ddisp - -sticky w -pady 10
8584     label $top.tabstopl -text "Tab spacing" -font optionfont
8585     spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
8586     grid x $top.tabstopl $top.tabstop -sticky w
8587     frame $top.ntag
8588     label $top.ntag.l -text "Display nearby tags" -font optionfont
8589     checkbutton $top.ntag.b -variable showneartags
8590     pack $top.ntag.b $top.ntag.l -side left
8591     grid x $top.ntag -sticky w
8592     frame $top.ldiff
8593     label $top.ldiff.l -text "Limit diffs to listed paths" -font optionfont
8594     checkbutton $top.ldiff.b -variable limitdiffs
8595     pack $top.ldiff.b $top.ldiff.l -side left
8596     grid x $top.ldiff -sticky w
8598     label $top.cdisp -text "Colors: press to choose"
8599     $top.cdisp configure -font uifont
8600     grid $top.cdisp - -sticky w -pady 10
8601     label $top.bg -padx 40 -relief sunk -background $bgcolor
8602     button $top.bgbut -text "Background" -font optionfont \
8603         -command [list choosecolor bgcolor 0 $top.bg background setbg]
8604     grid x $top.bgbut $top.bg -sticky w
8605     label $top.fg -padx 40 -relief sunk -background $fgcolor
8606     button $top.fgbut -text "Foreground" -font optionfont \
8607         -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
8608     grid x $top.fgbut $top.fg -sticky w
8609     label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
8610     button $top.diffoldbut -text "Diff: old lines" -font optionfont \
8611         -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
8612                       [list $ctext tag conf d0 -foreground]]
8613     grid x $top.diffoldbut $top.diffold -sticky w
8614     label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
8615     button $top.diffnewbut -text "Diff: new lines" -font optionfont \
8616         -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
8617                       [list $ctext tag conf d1 -foreground]]
8618     grid x $top.diffnewbut $top.diffnew -sticky w
8619     label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
8620     button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
8621         -command [list choosecolor diffcolors 2 $top.hunksep \
8622                       "diff hunk header" \
8623                       [list $ctext tag conf hunksep -foreground]]
8624     grid x $top.hunksepbut $top.hunksep -sticky w
8625     label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
8626     button $top.selbgbut -text "Select bg" -font optionfont \
8627         -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
8628     grid x $top.selbgbut $top.selbgsep -sticky w
8630     label $top.cfont -text "Fonts: press to choose"
8631     $top.cfont configure -font uifont
8632     grid $top.cfont - -sticky w -pady 10
8633     mkfontdisp mainfont $top "Main font"
8634     mkfontdisp textfont $top "Diff display font"
8635     mkfontdisp uifont $top "User interface font"
8637     frame $top.buts
8638     button $top.buts.ok -text "OK" -command prefsok -default active
8639     $top.buts.ok configure -font uifont
8640     button $top.buts.can -text "Cancel" -command prefscan -default normal
8641     $top.buts.can configure -font uifont
8642     grid $top.buts.ok $top.buts.can
8643     grid columnconfigure $top.buts 0 -weight 1 -uniform a
8644     grid columnconfigure $top.buts 1 -weight 1 -uniform a
8645     grid $top.buts - - -pady 10 -sticky ew
8646     bind $top <Visibility> "focus $top.buts.ok"
8649 proc choosecolor {v vi w x cmd} {
8650     global $v
8652     set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
8653                -title "Gitk: choose color for $x"]
8654     if {$c eq {}} return
8655     $w conf -background $c
8656     lset $v $vi $c
8657     eval $cmd $c
8660 proc setselbg {c} {
8661     global bglist cflist
8662     foreach w $bglist {
8663         $w configure -selectbackground $c
8664     }
8665     $cflist tag configure highlight \
8666         -background [$cflist cget -selectbackground]
8667     allcanvs itemconf secsel -fill $c
8670 proc setbg {c} {
8671     global bglist
8673     foreach w $bglist {
8674         $w conf -background $c
8675     }
8678 proc setfg {c} {
8679     global fglist canv
8681     foreach w $fglist {
8682         $w conf -foreground $c
8683     }
8684     allcanvs itemconf text -fill $c
8685     $canv itemconf circle -outline $c
8688 proc prefscan {} {
8689     global oldprefs prefstop
8691     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
8692                    limitdiffs tabstop} {
8693         global $v
8694         set $v $oldprefs($v)
8695     }
8696     catch {destroy $prefstop}
8697     unset prefstop
8698     fontcan
8701 proc prefsok {} {
8702     global maxwidth maxgraphpct
8703     global oldprefs prefstop showneartags showlocalchanges
8704     global fontpref mainfont textfont uifont
8705     global limitdiffs treediffs
8707     catch {destroy $prefstop}
8708     unset prefstop
8709     fontcan
8710     set fontchanged 0
8711     if {$mainfont ne $fontpref(mainfont)} {
8712         set mainfont $fontpref(mainfont)
8713         parsefont mainfont $mainfont
8714         eval font configure mainfont [fontflags mainfont]
8715         eval font configure mainfontbold [fontflags mainfont 1]
8716         setcoords
8717         set fontchanged 1
8718     }
8719     if {$textfont ne $fontpref(textfont)} {
8720         set textfont $fontpref(textfont)
8721         parsefont textfont $textfont
8722         eval font configure textfont [fontflags textfont]
8723         eval font configure textfontbold [fontflags textfont 1]
8724     }
8725     if {$uifont ne $fontpref(uifont)} {
8726         set uifont $fontpref(uifont)
8727         parsefont uifont $uifont
8728         eval font configure uifont [fontflags uifont]
8729     }
8730     settabs
8731     if {$showlocalchanges != $oldprefs(showlocalchanges)} {
8732         if {$showlocalchanges} {
8733             doshowlocalchanges
8734         } else {
8735             dohidelocalchanges
8736         }
8737     }
8738     if {$limitdiffs != $oldprefs(limitdiffs)} {
8739         # treediffs elements are limited by path
8740         catch {unset treediffs}
8741     }
8742     if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
8743         || $maxgraphpct != $oldprefs(maxgraphpct)} {
8744         redisplay
8745     } elseif {$showneartags != $oldprefs(showneartags) ||
8746           $limitdiffs != $oldprefs(limitdiffs)} {
8747         reselectline
8748     }
8751 proc formatdate {d} {
8752     global datetimeformat
8753     if {$d ne {}} {
8754         set d [clock format $d -format $datetimeformat]
8755     }
8756     return $d
8759 # This list of encoding names and aliases is distilled from
8760 # http://www.iana.org/assignments/character-sets.
8761 # Not all of them are supported by Tcl.
8762 set encoding_aliases {
8763     { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
8764       ISO646-US US-ASCII us IBM367 cp367 csASCII }
8765     { ISO-10646-UTF-1 csISO10646UTF1 }
8766     { ISO_646.basic:1983 ref csISO646basic1983 }
8767     { INVARIANT csINVARIANT }
8768     { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
8769     { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
8770     { NATS-SEFI iso-ir-8-1 csNATSSEFI }
8771     { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
8772     { NATS-DANO iso-ir-9-1 csNATSDANO }
8773     { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
8774     { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
8775     { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
8776     { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
8777     { ISO-2022-KR csISO2022KR }
8778     { EUC-KR csEUCKR }
8779     { ISO-2022-JP csISO2022JP }
8780     { ISO-2022-JP-2 csISO2022JP2 }
8781     { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
8782       csISO13JISC6220jp }
8783     { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
8784     { IT iso-ir-15 ISO646-IT csISO15Italian }
8785     { PT iso-ir-16 ISO646-PT csISO16Portuguese }
8786     { ES iso-ir-17 ISO646-ES csISO17Spanish }
8787     { greek7-old iso-ir-18 csISO18Greek7Old }
8788     { latin-greek iso-ir-19 csISO19LatinGreek }
8789     { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
8790     { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
8791     { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
8792     { ISO_5427 iso-ir-37 csISO5427Cyrillic }
8793     { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
8794     { BS_viewdata iso-ir-47 csISO47BSViewdata }
8795     { INIS iso-ir-49 csISO49INIS }
8796     { INIS-8 iso-ir-50 csISO50INIS8 }
8797     { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
8798     { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
8799     { ISO_5428:1980 iso-ir-55 csISO5428Greek }
8800     { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
8801     { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
8802     { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
8803       csISO60Norwegian1 }
8804     { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
8805     { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
8806     { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
8807     { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
8808     { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
8809     { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
8810     { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
8811     { greek7 iso-ir-88 csISO88Greek7 }
8812     { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
8813     { iso-ir-90 csISO90 }
8814     { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
8815     { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
8816       csISO92JISC62991984b }
8817     { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
8818     { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
8819     { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
8820       csISO95JIS62291984handadd }
8821     { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
8822     { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
8823     { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
8824     { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
8825       CP819 csISOLatin1 }
8826     { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
8827     { T.61-7bit iso-ir-102 csISO102T617bit }
8828     { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
8829     { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
8830     { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
8831     { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
8832     { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
8833     { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
8834     { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
8835     { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
8836       arabic csISOLatinArabic }
8837     { ISO_8859-6-E csISO88596E ISO-8859-6-E }
8838     { ISO_8859-6-I csISO88596I ISO-8859-6-I }
8839     { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
8840       greek greek8 csISOLatinGreek }
8841     { T.101-G2 iso-ir-128 csISO128T101G2 }
8842     { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
8843       csISOLatinHebrew }
8844     { ISO_8859-8-E csISO88598E ISO-8859-8-E }
8845     { ISO_8859-8-I csISO88598I ISO-8859-8-I }
8846     { CSN_369103 iso-ir-139 csISO139CSN369103 }
8847     { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
8848     { ISO_6937-2-add iso-ir-142 csISOTextComm }
8849     { IEC_P27-1 iso-ir-143 csISO143IECP271 }
8850     { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
8851       csISOLatinCyrillic }
8852     { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
8853     { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
8854     { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
8855     { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
8856     { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
8857     { ISO_6937-2-25 iso-ir-152 csISO6937Add }
8858     { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
8859     { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
8860     { ISO_10367-box iso-ir-155 csISO10367Box }
8861     { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
8862     { latin-lap lap iso-ir-158 csISO158Lap }
8863     { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
8864     { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
8865     { us-dk csUSDK }
8866     { dk-us csDKUS }
8867     { JIS_X0201 X0201 csHalfWidthKatakana }
8868     { KSC5636 ISO646-KR csKSC5636 }
8869     { ISO-10646-UCS-2 csUnicode }
8870     { ISO-10646-UCS-4 csUCS4 }
8871     { DEC-MCS dec csDECMCS }
8872     { hp-roman8 roman8 r8 csHPRoman8 }
8873     { macintosh mac csMacintosh }
8874     { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
8875       csIBM037 }
8876     { IBM038 EBCDIC-INT cp038 csIBM038 }
8877     { IBM273 CP273 csIBM273 }
8878     { IBM274 EBCDIC-BE CP274 csIBM274 }
8879     { IBM275 EBCDIC-BR cp275 csIBM275 }
8880     { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
8881     { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
8882     { IBM280 CP280 ebcdic-cp-it csIBM280 }
8883     { IBM281 EBCDIC-JP-E cp281 csIBM281 }
8884     { IBM284 CP284 ebcdic-cp-es csIBM284 }
8885     { IBM285 CP285 ebcdic-cp-gb csIBM285 }
8886     { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
8887     { IBM297 cp297 ebcdic-cp-fr csIBM297 }
8888     { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
8889     { IBM423 cp423 ebcdic-cp-gr csIBM423 }
8890     { IBM424 cp424 ebcdic-cp-he csIBM424 }
8891     { IBM437 cp437 437 csPC8CodePage437 }
8892     { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
8893     { IBM775 cp775 csPC775Baltic }
8894     { IBM850 cp850 850 csPC850Multilingual }
8895     { IBM851 cp851 851 csIBM851 }
8896     { IBM852 cp852 852 csPCp852 }
8897     { IBM855 cp855 855 csIBM855 }
8898     { IBM857 cp857 857 csIBM857 }
8899     { IBM860 cp860 860 csIBM860 }
8900     { IBM861 cp861 861 cp-is csIBM861 }
8901     { IBM862 cp862 862 csPC862LatinHebrew }
8902     { IBM863 cp863 863 csIBM863 }
8903     { IBM864 cp864 csIBM864 }
8904     { IBM865 cp865 865 csIBM865 }
8905     { IBM866 cp866 866 csIBM866 }
8906     { IBM868 CP868 cp-ar csIBM868 }
8907     { IBM869 cp869 869 cp-gr csIBM869 }
8908     { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
8909     { IBM871 CP871 ebcdic-cp-is csIBM871 }
8910     { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
8911     { IBM891 cp891 csIBM891 }
8912     { IBM903 cp903 csIBM903 }
8913     { IBM904 cp904 904 csIBBM904 }
8914     { IBM905 CP905 ebcdic-cp-tr csIBM905 }
8915     { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
8916     { IBM1026 CP1026 csIBM1026 }
8917     { EBCDIC-AT-DE csIBMEBCDICATDE }
8918     { EBCDIC-AT-DE-A csEBCDICATDEA }
8919     { EBCDIC-CA-FR csEBCDICCAFR }
8920     { EBCDIC-DK-NO csEBCDICDKNO }
8921     { EBCDIC-DK-NO-A csEBCDICDKNOA }
8922     { EBCDIC-FI-SE csEBCDICFISE }
8923     { EBCDIC-FI-SE-A csEBCDICFISEA }
8924     { EBCDIC-FR csEBCDICFR }
8925     { EBCDIC-IT csEBCDICIT }
8926     { EBCDIC-PT csEBCDICPT }
8927     { EBCDIC-ES csEBCDICES }
8928     { EBCDIC-ES-A csEBCDICESA }
8929     { EBCDIC-ES-S csEBCDICESS }
8930     { EBCDIC-UK csEBCDICUK }
8931     { EBCDIC-US csEBCDICUS }
8932     { UNKNOWN-8BIT csUnknown8BiT }
8933     { MNEMONIC csMnemonic }
8934     { MNEM csMnem }
8935     { VISCII csVISCII }
8936     { VIQR csVIQR }
8937     { KOI8-R csKOI8R }
8938     { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
8939     { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
8940     { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
8941     { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
8942     { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
8943     { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
8944     { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
8945     { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
8946     { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
8947     { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
8948     { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
8949     { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
8950     { IBM1047 IBM-1047 }
8951     { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
8952     { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
8953     { UNICODE-1-1 csUnicode11 }
8954     { CESU-8 csCESU-8 }
8955     { BOCU-1 csBOCU-1 }
8956     { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
8957     { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
8958       l8 }
8959     { ISO-8859-15 ISO_8859-15 Latin-9 }
8960     { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
8961     { GBK CP936 MS936 windows-936 }
8962     { JIS_Encoding csJISEncoding }
8963     { Shift_JIS MS_Kanji csShiftJIS }
8964     { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
8965       EUC-JP }
8966     { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
8967     { ISO-10646-UCS-Basic csUnicodeASCII }
8968     { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
8969     { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
8970     { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
8971     { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
8972     { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
8973     { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
8974     { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
8975     { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
8976     { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
8977     { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
8978     { Adobe-Standard-Encoding csAdobeStandardEncoding }
8979     { Ventura-US csVenturaUS }
8980     { Ventura-International csVenturaInternational }
8981     { PC8-Danish-Norwegian csPC8DanishNorwegian }
8982     { PC8-Turkish csPC8Turkish }
8983     { IBM-Symbols csIBMSymbols }
8984     { IBM-Thai csIBMThai }
8985     { HP-Legal csHPLegal }
8986     { HP-Pi-font csHPPiFont }
8987     { HP-Math8 csHPMath8 }
8988     { Adobe-Symbol-Encoding csHPPSMath }
8989     { HP-DeskTop csHPDesktop }
8990     { Ventura-Math csVenturaMath }
8991     { Microsoft-Publishing csMicrosoftPublishing }
8992     { Windows-31J csWindows31J }
8993     { GB2312 csGB2312 }
8994     { Big5 csBig5 }
8997 proc tcl_encoding {enc} {
8998     global encoding_aliases
8999     set names [encoding names]
9000     set lcnames [string tolower $names]
9001     set enc [string tolower $enc]
9002     set i [lsearch -exact $lcnames $enc]
9003     if {$i < 0} {
9004         # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
9005         if {[regsub {^iso[-_]} $enc iso encx]} {
9006             set i [lsearch -exact $lcnames $encx]
9007         }
9008     }
9009     if {$i < 0} {
9010         foreach l $encoding_aliases {
9011             set ll [string tolower $l]
9012             if {[lsearch -exact $ll $enc] < 0} continue
9013             # look through the aliases for one that tcl knows about
9014             foreach e $ll {
9015                 set i [lsearch -exact $lcnames $e]
9016                 if {$i < 0} {
9017                     if {[regsub {^iso[-_]} $e iso ex]} {
9018                         set i [lsearch -exact $lcnames $ex]
9019                     }
9020                 }
9021                 if {$i >= 0} break
9022             }
9023             break
9024         }
9025     }
9026     if {$i >= 0} {
9027         return [lindex $names $i]
9028     }
9029     return {}
9032 # First check that Tcl/Tk is recent enough
9033 if {[catch {package require Tk 8.4} err]} {
9034     show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
9035                      Gitk requires at least Tcl/Tk 8.4."
9036     exit 1
9039 # defaults...
9040 set datemode 0
9041 set wrcomcmd "git diff-tree --stdin -p --pretty"
9043 set gitencoding {}
9044 catch {
9045     set gitencoding [exec git config --get i18n.commitencoding]
9047 if {$gitencoding == ""} {
9048     set gitencoding "utf-8"
9050 set tclencoding [tcl_encoding $gitencoding]
9051 if {$tclencoding == {}} {
9052     puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
9055 set mainfont {Helvetica 9}
9056 set textfont {Courier 9}
9057 set uifont {Helvetica 9 bold}
9058 set tabstop 8
9059 set findmergefiles 0
9060 set maxgraphpct 50
9061 set maxwidth 16
9062 set revlistorder 0
9063 set fastdate 0
9064 set uparrowlen 5
9065 set downarrowlen 5
9066 set mingaplen 100
9067 set cmitmode "patch"
9068 set wrapcomment "none"
9069 set showneartags 1
9070 set maxrefs 20
9071 set maxlinelen 200
9072 set showlocalchanges 1
9073 set limitdiffs 1
9074 set datetimeformat "%Y-%m-%d %H:%M:%S"
9076 set colors {green red blue magenta darkgrey brown orange}
9077 set bgcolor white
9078 set fgcolor black
9079 set diffcolors {red "#00a000" blue}
9080 set diffcontext 3
9081 set selectbgcolor gray85
9083 catch {source ~/.gitk}
9085 font create optionfont -family sans-serif -size -12
9087 parsefont mainfont $mainfont
9088 eval font create mainfont [fontflags mainfont]
9089 eval font create mainfontbold [fontflags mainfont 1]
9091 parsefont textfont $textfont
9092 eval font create textfont [fontflags textfont]
9093 eval font create textfontbold [fontflags textfont 1]
9095 parsefont uifont $uifont
9096 eval font create uifont [fontflags uifont]
9098 # check that we can find a .git directory somewhere...
9099 if {[catch {set gitdir [gitdir]}]} {
9100     show_error {} . "Cannot find a git repository here."
9101     exit 1
9103 if {![file isdirectory $gitdir]} {
9104     show_error {} . "Cannot find the git directory \"$gitdir\"."
9105     exit 1
9108 set mergeonly 0
9109 set revtreeargs {}
9110 set cmdline_files {}
9111 set i 0
9112 foreach arg $argv {
9113     switch -- $arg {
9114         "" { }
9115         "-d" { set datemode 1 }
9116         "--merge" {
9117             set mergeonly 1
9118             lappend revtreeargs $arg
9119         }
9120         "--" {
9121             set cmdline_files [lrange $argv [expr {$i + 1}] end]
9122             break
9123         }
9124         default {
9125             lappend revtreeargs $arg
9126         }
9127     }
9128     incr i
9131 if {$i >= [llength $argv] && $revtreeargs ne {}} {
9132     # no -- on command line, but some arguments (other than -d)
9133     if {[catch {
9134         set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
9135         set cmdline_files [split $f "\n"]
9136         set n [llength $cmdline_files]
9137         set revtreeargs [lrange $revtreeargs 0 end-$n]
9138         # Unfortunately git rev-parse doesn't produce an error when
9139         # something is both a revision and a filename.  To be consistent
9140         # with git log and git rev-list, check revtreeargs for filenames.
9141         foreach arg $revtreeargs {
9142             if {[file exists $arg]} {
9143                 show_error {} . "Ambiguous argument '$arg': both revision\
9144                                  and filename"
9145                 exit 1
9146             }
9147         }
9148     } err]} {
9149         # unfortunately we get both stdout and stderr in $err,
9150         # so look for "fatal:".
9151         set i [string first "fatal:" $err]
9152         if {$i > 0} {
9153             set err [string range $err [expr {$i + 6}] end]
9154         }
9155         show_error {} . "Bad arguments to gitk:\n$err"
9156         exit 1
9157     }
9160 if {$mergeonly} {
9161     # find the list of unmerged files
9162     set mlist {}
9163     set nr_unmerged 0
9164     if {[catch {
9165         set fd [open "| git ls-files -u" r]
9166     } err]} {
9167         show_error {} . "Couldn't get list of unmerged files: $err"
9168         exit 1
9169     }
9170     while {[gets $fd line] >= 0} {
9171         set i [string first "\t" $line]
9172         if {$i < 0} continue
9173         set fname [string range $line [expr {$i+1}] end]
9174         if {[lsearch -exact $mlist $fname] >= 0} continue
9175         incr nr_unmerged
9176         if {$cmdline_files eq {} || [path_filter $cmdline_files $fname]} {
9177             lappend mlist $fname
9178         }
9179     }
9180     catch {close $fd}
9181     if {$mlist eq {}} {
9182         if {$nr_unmerged == 0} {
9183             show_error {} . "No files selected: --merge specified but\
9184                              no files are unmerged."
9185         } else {
9186             show_error {} . "No files selected: --merge specified but\
9187                              no unmerged files are within file limit."
9188         }
9189         exit 1
9190     }
9191     set cmdline_files $mlist
9194 set nullid "0000000000000000000000000000000000000000"
9195 set nullid2 "0000000000000000000000000000000000000001"
9197 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
9199 set runq {}
9200 set history {}
9201 set historyindex 0
9202 set fh_serial 0
9203 set nhl_names {}
9204 set highlight_paths {}
9205 set findpattern {}
9206 set searchdirn -forwards
9207 set boldrows {}
9208 set boldnamerows {}
9209 set diffelide {0 0}
9210 set markingmatches 0
9211 set linkentercount 0
9212 set need_redisplay 0
9213 set nrows_drawn 0
9214 set firsttabstop 0
9216 set nextviewnum 1
9217 set curview 0
9218 set selectedview 0
9219 set selectedhlview None
9220 set highlight_related None
9221 set highlight_files {}
9222 set viewfiles(0) {}
9223 set viewperm(0) 0
9224 set viewargs(0) {}
9226 set loginstance 0
9227 set cmdlineok 0
9228 set stopped 0
9229 set stuffsaved 0
9230 set patchnum 0
9231 set lserial 0
9232 setcoords
9233 makewindow
9234 # wait for the window to become visible
9235 tkwait visibility .
9236 wm title . "[file tail $argv0]: [file tail [pwd]]"
9237 readrefs
9239 if {$cmdline_files ne {} || $revtreeargs ne {}} {
9240     # create a view for the files/dirs specified on the command line
9241     set curview 1
9242     set selectedview 1
9243     set nextviewnum 2
9244     set viewname(1) "Command line"
9245     set viewfiles(1) $cmdline_files
9246     set viewargs(1) $revtreeargs
9247     set viewperm(1) 0
9248     addviewmenu 1
9249     .bar.view entryconf Edit* -state normal
9250     .bar.view entryconf Delete* -state normal
9253 if {[info exists permviews]} {
9254     foreach v $permviews {
9255         set n $nextviewnum
9256         incr nextviewnum
9257         set viewname($n) [lindex $v 0]
9258         set viewfiles($n) [lindex $v 1]
9259         set viewargs($n) [lindex $v 2]
9260         set viewperm($n) 1
9261         addviewmenu $n
9262     }
9264 getcommits