Code

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