Code

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