Code

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