Merge branch 'dev'
[git] / gitk
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
4
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.
9
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 }
18
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
26
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 }
35
36 proc filerun {fd script} {
37     fileevent $fd readable [list filereadable $fd $script]
38 }
39
40 proc filereadable {fd script} {
41     global runq currunq
42
43     fileevent $fd readable {}
44     if {$runq eq {} && ![info exists currunq]} {
45         after idle dorunq
46     }
47     lappend runq [list $fd $script]
48 }
49
50 proc nukefile {fd} {
51     global runq
52
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 }
61
62 proc dorunq {} {
63     global isonrunq runq currunq
64
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 }
94
95 proc reg_instance {fd} {
96     global commfd leftover loginstance
97
98     set i [incr loginstance]
99     set commfd($i) $fd
100     set leftover($i) {}
101     return $i
102 }
103
104 proc unmerged_files {files} {
105     global nr_unmerged
106
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
128 }
129
130 proc parseviewargs {n arglist} {
131     global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
132
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
240 }
241
242 proc parseviewrevs {view revs} {
243     global vposids vnegids
244
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
303 }
304
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
314
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
321
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]
333
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
350
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     }
360
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
382 }
383
384 proc stop_instance {inst} {
385     global commfd leftover
386
387     set fd $commfd($inst)
388     catch {
389         set pid [pid $fd]
390
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)
401 }
402
403 proc stop_backends {} {
404     global commfd
405
406     foreach inst [array names commfd] {
407         stop_instance $inst
408     }
409 }
410
411 proc stop_rev_list {view} {
412     global viewinstances
413
414     foreach inst $viewinstances($view) {
415         stop_instance $inst
416     }
417     set viewinstances($view) {}
418 }
419
420 proc reset_pending_select {selid} {
421     global pending_select mainheadid selectheadid
422
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     }
430 }
431
432 proc getcommits {selid} {
433     global canv curview need_redisplay viewactive
434
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     }
443 }
444
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
452
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     }
523 }
524
525 proc reloadcommits {} {
526     global curview viewcomplete selectedline currentid thickerline
527     global showneartags treediffs commitinterest cached_commitrow
528     global targetid
529
530     set selid {}
531     if {$selectedline ne {}} {
532         set selid $currentid
533     }
534
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
555 }
556
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]
568 }
569
570 # Procedures used in reordering commits from git log (without
571 # --topo-order) into the order for display.
572
573 proc varcinit {view} {
574     global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
575     global vtokmod varcmod vrowmod varcix vlastins
576
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}
589 }
590
591 proc resetvarcs {view} {
592     global varcid varccommits parents children vseedcount ordertok
593
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}
610 }
611
612 # returns a list of the commits with no children
613 proc seeds {v} {
614     global vdownptr vleftptr varcstart
615
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
623 }
624
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
629
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
696 }
697
698 proc splitvarc {p v} {
699     global varcid varcstart varccommits varctok
700     global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
701
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     }
728 }
729
730 proc renumbervarc {a v} {
731     global parents children varctok varcstart varccommits
732     global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
733
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"
857 }
858
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
864
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     }
876 }
877
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
882
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     }
910 }
911
912 proc insertfakerow {id p} {
913     global varcid varccommits parents children cmitlisted
914     global commitidx varctok vtokmod targetid targetrow curview numcommits
915
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
939 }
940
941 proc removefakerow {id} {
942     global varcid varccommits parents children commitidx
943     global varctok vtokmod cmitlisted currentid selectedline
944     global targetid curview numcommits
945
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
978 }
979
980 proc first_real_child {vp} {
981     global children nullid nullid2
982
983     foreach id $children($vp) {
984         if {$id ne $nullid && $id ne $nullid2} {
985             return $id
986         }
987     }
988     return {}
989 }
990
991 proc last_real_child {vp} {
992     global children nullid nullid2
993
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 {}
1002 }
1003
1004 proc vtokcmp {v a b} {
1005     global varctok varcid
1006
1007     return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1008                 [lindex $varctok($v) $varcid($v,$b)]]
1009 }
1010
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
1015
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     }
1041 }
1042
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
1048
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     }
1108 }
1109
1110 # Test whether view $v contains commit $id
1111 proc commitinview {id v} {
1112     global varcid
1113
1114     return [info exists varcid($v,$id)]
1115 }
1116
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
1121
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
1142 }
1143
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
1147
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]}]
1158 }
1159
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
1178 }
1179
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
1185
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     }
1219 }
1220
1221 proc commitonrow {row} {
1222     global displayorder
1223
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
1230 }
1231
1232 proc closevarcs {v} {
1233     global varctok varccommits varcid parents children
1234     global cmitlisted commitidx vtokmod
1235
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     }
1267 }
1268
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
1273
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     }
1293 }
1294
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
1302
1303     lappend commitinterest([string range $id 0 3]) $id $cmd
1304 }
1305
1306 proc check_interest {id scripts} {
1307     global commitinterest
1308
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
1326 }
1327
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
1334
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
1432
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         }
1451
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         }
1487
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         }
1504
1505         set scripts [check_interest $id $scripts]
1506         set gotsome 1
1507     }
1508     if {$gotsome} {
1509         global numcommits hlview
1510
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
1524 }
1525
1526 proc chewcommits {} {
1527     global curview hlview viewcomplete
1528     global pending_select
1529
1530     layoutmore
1531     if {$viewcomplete($curview)} {
1532         global commitidx varctok
1533         global numcommits startmsecs
1534
1535         if {[info exists pending_select]} {
1536             update
1537             reset_pending_select {}
1538
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
1556 }
1557
1558 proc readcommit {id} {
1559     if {[catch {set contents [exec git cat-file commit $id]}]} return
1560     parsecommit $id $contents 0
1561 }
1562
1563 proc parsecommit {id contents listed} {
1564     global commitinfo cdate
1565
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]
1618 }
1619
1620 proc getcommit {id} {
1621     global commitdata commitinfo
1622
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
1632 }
1633
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
1639
1640     set ids {}
1641     foreach match [array names varcid "$curview,$prefix*"] {
1642         lappend ids [lindex [split $match ","] 1]
1643     }
1644     return $ids
1645 }
1646
1647 proc readrefs {} {
1648     global tagids idtags headids idheads tagobjid
1649     global otherrefids idotherrefs mainhead mainheadid
1650     global selecthead selectheadid
1651
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     }
1703 }
1704
1705 # skip over fake commits
1706 proc first_real_row {} {
1707     global nullid nullid2 numcommits
1708
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
1716 }
1717
1718 # update things for a head moved to a child of its previous location
1719 proc movehead {id name} {
1720     global headids idheads
1721
1722     removehead $headids($name) $name
1723     set headids($name) $id
1724     lappend idheads($id) $name
1725 }
1726
1727 # update things when a head has been removed
1728 proc removehead {id name} {
1729     global headids idheads
1730
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)
1740 }
1741
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     bind $top <Key-space>  "destroy $top"
1750     bind $top <Key-Escape> "destroy $top"
1751     tkwait window $top
1752 }
1753
1754 proc error_popup {msg {owner .}} {
1755     set w .error
1756     toplevel $w
1757     wm transient $w $owner
1758     show_error $w $w $msg
1759 }
1760
1761 proc confirm_popup {msg {owner .}} {
1762     global confirm_ok
1763     set confirm_ok 0
1764     set w .confirm
1765     toplevel $w
1766     wm transient $w $owner
1767     message $w.m -text $msg -justify center -aspect 400
1768     pack $w.m -side top -fill x -padx 20 -pady 20
1769     button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1770     pack $w.ok -side left -fill x
1771     button $w.cancel -text [mc Cancel] -command "destroy $w"
1772     pack $w.cancel -side right -fill x
1773     bind $w <Visibility> "grab $w; focus $w"
1774     bind $w <Key-Return> "set confirm_ok 1; destroy $w"
1775     bind $w <Key-space>  "set confirm_ok 1; destroy $w"
1776     bind $w <Key-Escape> "destroy $w"
1777     tkwait window $w
1778     return $confirm_ok
1779 }
1780
1781 proc setoptions {} {
1782     option add *Panedwindow.showHandle 1 startupFile
1783     option add *Panedwindow.sashRelief raised startupFile
1784     option add *Button.font uifont startupFile
1785     option add *Checkbutton.font uifont startupFile
1786     option add *Radiobutton.font uifont startupFile
1787     option add *Menu.font uifont startupFile
1788     option add *Menubutton.font uifont startupFile
1789     option add *Label.font uifont startupFile
1790     option add *Message.font uifont startupFile
1791     option add *Entry.font uifont startupFile
1792 }
1793
1794 # Make a menu and submenus.
1795 # m is the window name for the menu, items is the list of menu items to add.
1796 # Each item is a list {mc label type description options...}
1797 # mc is ignored; it's so we can put mc there to alert xgettext
1798 # label is the string that appears in the menu
1799 # type is cascade, command or radiobutton (should add checkbutton)
1800 # description depends on type; it's the sublist for cascade, the
1801 # command to invoke for command, or {variable value} for radiobutton
1802 proc makemenu {m items} {
1803     menu $m
1804     foreach i $items {
1805         set name [mc [lindex $i 1]]
1806         set type [lindex $i 2]
1807         set thing [lindex $i 3]
1808         set params [list $type]
1809         if {$name ne {}} {
1810             set u [string first "&" [string map {&& x} $name]]
1811             lappend params -label [string map {&& & & {}} $name]
1812             if {$u >= 0} {
1813                 lappend params -underline $u
1814             }
1815         }
1816         switch -- $type {
1817             "cascade" {
1818                 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1819                 lappend params -menu $m.$submenu
1820             }
1821             "command" {
1822                 lappend params -command $thing
1823             }
1824             "radiobutton" {
1825                 lappend params -variable [lindex $thing 0] \
1826                     -value [lindex $thing 1]
1827             }
1828         }
1829         eval $m add $params [lrange $i 4 end]
1830         if {$type eq "cascade"} {
1831             makemenu $m.$submenu $thing
1832         }
1833     }
1834 }
1835
1836 # translate string and remove ampersands
1837 proc mca {str} {
1838     return [string map {&& & & {}} [mc $str]]
1839 }
1840
1841 proc makewindow {} {
1842     global canv canv2 canv3 linespc charspc ctext cflist cscroll
1843     global tabstop
1844     global findtype findtypemenu findloc findstring fstring geometry
1845     global entries sha1entry sha1string sha1but
1846     global diffcontextstring diffcontext
1847     global ignorespace
1848     global maincursor textcursor curtextcursor
1849     global rowctxmenu fakerowmenu mergemax wrapcomment
1850     global highlight_files gdttype
1851     global searchstring sstring
1852     global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1853     global headctxmenu progresscanv progressitem progresscoords statusw
1854     global fprogitem fprogcoord lastprogupdate progupdatepending
1855     global rprogitem rprogcoord rownumsel numcommits
1856     global have_tk85
1857
1858     # The "mc" arguments here are purely so that xgettext
1859     # sees the following string as needing to be translated
1860     makemenu .bar {
1861         {mc "File" cascade {
1862             {mc "Update" command updatecommits -accelerator F5}
1863             {mc "Reload" command reloadcommits}
1864             {mc "Reread references" command rereadrefs}
1865             {mc "List references" command showrefs}
1866             {mc "Quit" command doquit}
1867         }}
1868         {mc "Edit" cascade {
1869             {mc "Preferences" command doprefs}
1870         }}
1871         {mc "View" cascade {
1872             {mc "New view..." command {newview 0}}
1873             {mc "Edit view..." command editview -state disabled}
1874             {mc "Delete view" command delview -state disabled}
1875             {xx "" separator}
1876             {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
1877         }}
1878         {mc "Help" cascade {
1879             {mc "About gitk" command about}
1880             {mc "Key bindings" command keys}
1881         }}
1882     }
1883     . configure -menu .bar
1884
1885     # the gui has upper and lower half, parts of a paned window.
1886     panedwindow .ctop -orient vertical
1887
1888     # possibly use assumed geometry
1889     if {![info exists geometry(pwsash0)]} {
1890         set geometry(topheight) [expr {15 * $linespc}]
1891         set geometry(topwidth) [expr {80 * $charspc}]
1892         set geometry(botheight) [expr {15 * $linespc}]
1893         set geometry(botwidth) [expr {50 * $charspc}]
1894         set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1895         set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1896     }
1897
1898     # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1899     frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1900     frame .tf.histframe
1901     panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1902
1903     # create three canvases
1904     set cscroll .tf.histframe.csb
1905     set canv .tf.histframe.pwclist.canv
1906     canvas $canv \
1907         -selectbackground $selectbgcolor \
1908         -background $bgcolor -bd 0 \
1909         -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1910     .tf.histframe.pwclist add $canv
1911     set canv2 .tf.histframe.pwclist.canv2
1912     canvas $canv2 \
1913         -selectbackground $selectbgcolor \
1914         -background $bgcolor -bd 0 -yscrollincr $linespc
1915     .tf.histframe.pwclist add $canv2
1916     set canv3 .tf.histframe.pwclist.canv3
1917     canvas $canv3 \
1918         -selectbackground $selectbgcolor \
1919         -background $bgcolor -bd 0 -yscrollincr $linespc
1920     .tf.histframe.pwclist add $canv3
1921     eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1922     eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1923
1924     # a scroll bar to rule them
1925     scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1926     pack $cscroll -side right -fill y
1927     bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1928     lappend bglist $canv $canv2 $canv3
1929     pack .tf.histframe.pwclist -fill both -expand 1 -side left
1930
1931     # we have two button bars at bottom of top frame. Bar 1
1932     frame .tf.bar
1933     frame .tf.lbar -height 15
1934
1935     set sha1entry .tf.bar.sha1
1936     set entries $sha1entry
1937     set sha1but .tf.bar.sha1label
1938     button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1939         -command gotocommit -width 8
1940     $sha1but conf -disabledforeground [$sha1but cget -foreground]
1941     pack .tf.bar.sha1label -side left
1942     entry $sha1entry -width 40 -font textfont -textvariable sha1string
1943     trace add variable sha1string write sha1change
1944     pack $sha1entry -side left -pady 2
1945
1946     image create bitmap bm-left -data {
1947         #define left_width 16
1948         #define left_height 16
1949         static unsigned char left_bits[] = {
1950         0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1951         0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1952         0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1953     }
1954     image create bitmap bm-right -data {
1955         #define right_width 16
1956         #define right_height 16
1957         static unsigned char right_bits[] = {
1958         0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1959         0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1960         0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1961     }
1962     button .tf.bar.leftbut -image bm-left -command goback \
1963         -state disabled -width 26
1964     pack .tf.bar.leftbut -side left -fill y
1965     button .tf.bar.rightbut -image bm-right -command goforw \
1966         -state disabled -width 26
1967     pack .tf.bar.rightbut -side left -fill y
1968
1969     label .tf.bar.rowlabel -text [mc "Row"]
1970     set rownumsel {}
1971     label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
1972         -relief sunken -anchor e
1973     label .tf.bar.rowlabel2 -text "/"
1974     label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
1975         -relief sunken -anchor e
1976     pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
1977         -side left
1978     global selectedline
1979     trace add variable selectedline write selectedline_change
1980
1981     # Status label and progress bar
1982     set statusw .tf.bar.status
1983     label $statusw -width 15 -relief sunken
1984     pack $statusw -side left -padx 5
1985     set h [expr {[font metrics uifont -linespace] + 2}]
1986     set progresscanv .tf.bar.progress
1987     canvas $progresscanv -relief sunken -height $h -borderwidth 2
1988     set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1989     set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1990     set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1991     pack $progresscanv -side right -expand 1 -fill x
1992     set progresscoords {0 0}
1993     set fprogcoord 0
1994     set rprogcoord 0
1995     bind $progresscanv <Configure> adjustprogress
1996     set lastprogupdate [clock clicks -milliseconds]
1997     set progupdatepending 0
1998
1999     # build up the bottom bar of upper window
2000     label .tf.lbar.flabel -text "[mc "Find"] "
2001     button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
2002     button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
2003     label .tf.lbar.flab2 -text " [mc "commit"] "
2004     pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2005         -side left -fill y
2006     set gdttype [mc "containing:"]
2007     set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
2008                 [mc "containing:"] \
2009                 [mc "touching paths:"] \
2010                 [mc "adding/removing string:"]]
2011     trace add variable gdttype write gdttype_change
2012     pack .tf.lbar.gdttype -side left -fill y
2013
2014     set findstring {}
2015     set fstring .tf.lbar.findstring
2016     lappend entries $fstring
2017     entry $fstring -width 30 -font textfont -textvariable findstring
2018     trace add variable findstring write find_change
2019     set findtype [mc "Exact"]
2020     set findtypemenu [tk_optionMenu .tf.lbar.findtype \
2021                       findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2022     trace add variable findtype write findcom_change
2023     set findloc [mc "All fields"]
2024     tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2025         [mc "Comments"] [mc "Author"] [mc "Committer"]
2026     trace add variable findloc write find_change
2027     pack .tf.lbar.findloc -side right
2028     pack .tf.lbar.findtype -side right
2029     pack $fstring -side left -expand 1 -fill x
2030
2031     # Finish putting the upper half of the viewer together
2032     pack .tf.lbar -in .tf -side bottom -fill x
2033     pack .tf.bar -in .tf -side bottom -fill x
2034     pack .tf.histframe -fill both -side top -expand 1
2035     .ctop add .tf
2036     .ctop paneconfigure .tf -height $geometry(topheight)
2037     .ctop paneconfigure .tf -width $geometry(topwidth)
2038
2039     # now build up the bottom
2040     panedwindow .pwbottom -orient horizontal
2041
2042     # lower left, a text box over search bar, scroll bar to the right
2043     # if we know window height, then that will set the lower text height, otherwise
2044     # we set lower text height which will drive window height
2045     if {[info exists geometry(main)]} {
2046         frame .bleft -width $geometry(botwidth)
2047     } else {
2048         frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2049     }
2050     frame .bleft.top
2051     frame .bleft.mid
2052     frame .bleft.bottom
2053
2054     button .bleft.top.search -text [mc "Search"] -command dosearch
2055     pack .bleft.top.search -side left -padx 5
2056     set sstring .bleft.top.sstring
2057     entry $sstring -width 20 -font textfont -textvariable searchstring
2058     lappend entries $sstring
2059     trace add variable searchstring write incrsearch
2060     pack $sstring -side left -expand 1 -fill x
2061     radiobutton .bleft.mid.diff -text [mc "Diff"] \
2062         -command changediffdisp -variable diffelide -value {0 0}
2063     radiobutton .bleft.mid.old -text [mc "Old version"] \
2064         -command changediffdisp -variable diffelide -value {0 1}
2065     radiobutton .bleft.mid.new -text [mc "New version"] \
2066         -command changediffdisp -variable diffelide -value {1 0}
2067     label .bleft.mid.labeldiffcontext -text "      [mc "Lines of context"]: "
2068     pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2069     spinbox .bleft.mid.diffcontext -width 5 -font textfont \
2070         -from 1 -increment 1 -to 10000000 \
2071         -validate all -validatecommand "diffcontextvalidate %P" \
2072         -textvariable diffcontextstring
2073     .bleft.mid.diffcontext set $diffcontext
2074     trace add variable diffcontextstring write diffcontextchange
2075     lappend entries .bleft.mid.diffcontext
2076     pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2077     checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2078         -command changeignorespace -variable ignorespace
2079     pack .bleft.mid.ignspace -side left -padx 5
2080     set ctext .bleft.bottom.ctext
2081     text $ctext -background $bgcolor -foreground $fgcolor \
2082         -state disabled -font textfont \
2083         -yscrollcommand scrolltext -wrap none \
2084         -xscrollcommand ".bleft.bottom.sbhorizontal set"
2085     if {$have_tk85} {
2086         $ctext conf -tabstyle wordprocessor
2087     }
2088     scrollbar .bleft.bottom.sb -command "$ctext yview"
2089     scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
2090         -width 10
2091     pack .bleft.top -side top -fill x
2092     pack .bleft.mid -side top -fill x
2093     grid $ctext .bleft.bottom.sb -sticky nsew
2094     grid .bleft.bottom.sbhorizontal -sticky ew
2095     grid columnconfigure .bleft.bottom 0 -weight 1
2096     grid rowconfigure .bleft.bottom 0 -weight 1
2097     grid rowconfigure .bleft.bottom 1 -weight 0
2098     pack .bleft.bottom -side top -fill both -expand 1
2099     lappend bglist $ctext
2100     lappend fglist $ctext
2101
2102     $ctext tag conf comment -wrap $wrapcomment
2103     $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2104     $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2105     $ctext tag conf d0 -fore [lindex $diffcolors 0]
2106     $ctext tag conf dresult -fore [lindex $diffcolors 1]
2107     $ctext tag conf m0 -fore red
2108     $ctext tag conf m1 -fore blue
2109     $ctext tag conf m2 -fore green
2110     $ctext tag conf m3 -fore purple
2111     $ctext tag conf m4 -fore brown
2112     $ctext tag conf m5 -fore "#009090"
2113     $ctext tag conf m6 -fore magenta
2114     $ctext tag conf m7 -fore "#808000"
2115     $ctext tag conf m8 -fore "#009000"
2116     $ctext tag conf m9 -fore "#ff0080"
2117     $ctext tag conf m10 -fore cyan
2118     $ctext tag conf m11 -fore "#b07070"
2119     $ctext tag conf m12 -fore "#70b0f0"
2120     $ctext tag conf m13 -fore "#70f0b0"
2121     $ctext tag conf m14 -fore "#f0b070"
2122     $ctext tag conf m15 -fore "#ff70b0"
2123     $ctext tag conf mmax -fore darkgrey
2124     set mergemax 16
2125     $ctext tag conf mresult -font textfontbold
2126     $ctext tag conf msep -font textfontbold
2127     $ctext tag conf found -back yellow
2128
2129     .pwbottom add .bleft
2130     .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2131
2132     # lower right
2133     frame .bright
2134     frame .bright.mode
2135     radiobutton .bright.mode.patch -text [mc "Patch"] \
2136         -command reselectline -variable cmitmode -value "patch"
2137     radiobutton .bright.mode.tree -text [mc "Tree"] \
2138         -command reselectline -variable cmitmode -value "tree"
2139     grid .bright.mode.patch .bright.mode.tree -sticky ew
2140     pack .bright.mode -side top -fill x
2141     set cflist .bright.cfiles
2142     set indent [font measure mainfont "nn"]
2143     text $cflist \
2144         -selectbackground $selectbgcolor \
2145         -background $bgcolor -foreground $fgcolor \
2146         -font mainfont \
2147         -tabs [list $indent [expr {2 * $indent}]] \
2148         -yscrollcommand ".bright.sb set" \
2149         -cursor [. cget -cursor] \
2150         -spacing1 1 -spacing3 1
2151     lappend bglist $cflist
2152     lappend fglist $cflist
2153     scrollbar .bright.sb -command "$cflist yview"
2154     pack .bright.sb -side right -fill y
2155     pack $cflist -side left -fill both -expand 1
2156     $cflist tag configure highlight \
2157         -background [$cflist cget -selectbackground]
2158     $cflist tag configure bold -font mainfontbold
2159
2160     .pwbottom add .bright
2161     .ctop add .pwbottom
2162
2163     # restore window width & height if known
2164     if {[info exists geometry(main)]} {
2165         if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2166             if {$w > [winfo screenwidth .]} {
2167                 set w [winfo screenwidth .]
2168             }
2169             if {$h > [winfo screenheight .]} {
2170                 set h [winfo screenheight .]
2171             }
2172             wm geometry . "${w}x$h"
2173         }
2174     }
2175
2176     if {[tk windowingsystem] eq {aqua}} {
2177         set M1B M1
2178     } else {
2179         set M1B Control
2180     }
2181
2182     bind .pwbottom <Configure> {resizecdetpanes %W %w}
2183     pack .ctop -fill both -expand 1
2184     bindall <1> {selcanvline %W %x %y}
2185     #bindall <B1-Motion> {selcanvline %W %x %y}
2186     if {[tk windowingsystem] == "win32"} {
2187         bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2188         bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2189     } else {
2190         bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2191         bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2192         if {[tk windowingsystem] eq "aqua"} {
2193             bindall <MouseWheel> {
2194                 set delta [expr {- (%D)}]
2195                 allcanvs yview scroll $delta units
2196             }
2197         }
2198     }
2199     bindall <2> "canvscan mark %W %x %y"
2200     bindall <B2-Motion> "canvscan dragto %W %x %y"
2201     bindkey <Home> selfirstline
2202     bindkey <End> sellastline
2203     bind . <Key-Up> "selnextline -1"
2204     bind . <Key-Down> "selnextline 1"
2205     bind . <Shift-Key-Up> "dofind -1 0"
2206     bind . <Shift-Key-Down> "dofind 1 0"
2207     bindkey <Key-Right> "goforw"
2208     bindkey <Key-Left> "goback"
2209     bind . <Key-Prior> "selnextpage -1"
2210     bind . <Key-Next> "selnextpage 1"
2211     bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2212     bind . <$M1B-End> "allcanvs yview moveto 1.0"
2213     bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2214     bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2215     bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2216     bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2217     bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2218     bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2219     bindkey <Key-space> "$ctext yview scroll 1 pages"
2220     bindkey p "selnextline -1"
2221     bindkey n "selnextline 1"
2222     bindkey z "goback"
2223     bindkey x "goforw"
2224     bindkey i "selnextline -1"
2225     bindkey k "selnextline 1"
2226     bindkey j "goback"
2227     bindkey l "goforw"
2228     bindkey b prevfile
2229     bindkey d "$ctext yview scroll 18 units"
2230     bindkey u "$ctext yview scroll -18 units"
2231     bindkey / {dofind 1 1}
2232     bindkey <Key-Return> {dofind 1 1}
2233     bindkey ? {dofind -1 1}
2234     bindkey f nextfile
2235     bindkey <F5> updatecommits
2236     bind . <$M1B-q> doquit
2237     bind . <$M1B-f> {dofind 1 1}
2238     bind . <$M1B-g> {dofind 1 0}
2239     bind . <$M1B-r> dosearchback
2240     bind . <$M1B-s> dosearch
2241     bind . <$M1B-equal> {incrfont 1}
2242     bind . <$M1B-plus> {incrfont 1}
2243     bind . <$M1B-KP_Add> {incrfont 1}
2244     bind . <$M1B-minus> {incrfont -1}
2245     bind . <$M1B-KP_Subtract> {incrfont -1}
2246     wm protocol . WM_DELETE_WINDOW doquit
2247     bind . <Destroy> {stop_backends}
2248     bind . <Button-1> "click %W"
2249     bind $fstring <Key-Return> {dofind 1 1}
2250     bind $sha1entry <Key-Return> {gotocommit; break}
2251     bind $sha1entry <<PasteSelection>> clearsha1
2252     bind $cflist <1> {sel_flist %W %x %y; break}
2253     bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2254     bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2255     global ctxbut
2256     bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2257     bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2258
2259     set maincursor [. cget -cursor]
2260     set textcursor [$ctext cget -cursor]
2261     set curtextcursor $textcursor
2262
2263     set rowctxmenu .rowctxmenu
2264     makemenu $rowctxmenu {
2265         {mc "Diff this -> selected" command {diffvssel 0}}
2266         {mc "Diff selected -> this" command {diffvssel 1}}
2267         {mc "Make patch" command mkpatch}
2268         {mc "Create tag" command mktag}
2269         {mc "Write commit to file" command writecommit}
2270         {mc "Create new branch" command mkbranch}
2271         {mc "Cherry-pick this commit" command cherrypick}
2272         {mc "Reset HEAD branch to here" command resethead}
2273     }
2274     $rowctxmenu configure -tearoff 0
2275
2276     set fakerowmenu .fakerowmenu
2277     makemenu $fakerowmenu {
2278         {mc "Diff this -> selected" command {diffvssel 0}}
2279         {mc "Diff selected -> this" command {diffvssel 1}}
2280         {mc "Make patch" command mkpatch}
2281     }
2282     $fakerowmenu configure -tearoff 0
2283
2284     set headctxmenu .headctxmenu
2285     makemenu $headctxmenu {
2286         {mc "Check out this branch" command cobranch}
2287         {mc "Remove this branch" command rmbranch}
2288     }
2289     $headctxmenu configure -tearoff 0
2290
2291     global flist_menu
2292     set flist_menu .flistctxmenu
2293     makemenu $flist_menu {
2294         {mc "Highlight this too" command {flist_hl 0}}
2295         {mc "Highlight this only" command {flist_hl 1}}
2296         {mc "External diff" command {external_diff}}
2297         {mc "Blame parent commit" command {external_blame 1}}
2298     }
2299     $flist_menu configure -tearoff 0
2300
2301     global diff_menu
2302     set diff_menu .diffctxmenu
2303     makemenu $diff_menu {
2304         {mc "Show origin of this line" command show_line_source}
2305         {mc "Run git gui blame on this line" command {external_blame_diff}}
2306     }
2307     $diff_menu configure -tearoff 0
2308 }
2309
2310 # Windows sends all mouse wheel events to the current focused window, not
2311 # the one where the mouse hovers, so bind those events here and redirect
2312 # to the correct window
2313 proc windows_mousewheel_redirector {W X Y D} {
2314     global canv canv2 canv3
2315     set w [winfo containing -displayof $W $X $Y]
2316     if {$w ne ""} {
2317         set u [expr {$D < 0 ? 5 : -5}]
2318         if {$w == $canv || $w == $canv2 || $w == $canv3} {
2319             allcanvs yview scroll $u units
2320         } else {
2321             catch {
2322                 $w yview scroll $u units
2323             }
2324         }
2325     }
2326 }
2327
2328 # Update row number label when selectedline changes
2329 proc selectedline_change {n1 n2 op} {
2330     global selectedline rownumsel
2331
2332     if {$selectedline eq {}} {
2333         set rownumsel {}
2334     } else {
2335         set rownumsel [expr {$selectedline + 1}]
2336     }
2337 }
2338
2339 # mouse-2 makes all windows scan vertically, but only the one
2340 # the cursor is in scans horizontally
2341 proc canvscan {op w x y} {
2342     global canv canv2 canv3
2343     foreach c [list $canv $canv2 $canv3] {
2344         if {$c == $w} {
2345             $c scan $op $x $y
2346         } else {
2347             $c scan $op 0 $y
2348         }
2349     }
2350 }
2351
2352 proc scrollcanv {cscroll f0 f1} {
2353     $cscroll set $f0 $f1
2354     drawvisible
2355     flushhighlights
2356 }
2357
2358 # when we make a key binding for the toplevel, make sure
2359 # it doesn't get triggered when that key is pressed in the
2360 # find string entry widget.
2361 proc bindkey {ev script} {
2362     global entries
2363     bind . $ev $script
2364     set escript [bind Entry $ev]
2365     if {$escript == {}} {
2366         set escript [bind Entry <Key>]
2367     }
2368     foreach e $entries {
2369         bind $e $ev "$escript; break"
2370     }
2371 }
2372
2373 # set the focus back to the toplevel for any click outside
2374 # the entry widgets
2375 proc click {w} {
2376     global ctext entries
2377     foreach e [concat $entries $ctext] {
2378         if {$w == $e} return
2379     }
2380     focus .
2381 }
2382
2383 # Adjust the progress bar for a change in requested extent or canvas size
2384 proc adjustprogress {} {
2385     global progresscanv progressitem progresscoords
2386     global fprogitem fprogcoord lastprogupdate progupdatepending
2387     global rprogitem rprogcoord
2388
2389     set w [expr {[winfo width $progresscanv] - 4}]
2390     set x0 [expr {$w * [lindex $progresscoords 0]}]
2391     set x1 [expr {$w * [lindex $progresscoords 1]}]
2392     set h [winfo height $progresscanv]
2393     $progresscanv coords $progressitem $x0 0 $x1 $h
2394     $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2395     $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2396     set now [clock clicks -milliseconds]
2397     if {$now >= $lastprogupdate + 100} {
2398         set progupdatepending 0
2399         update
2400     } elseif {!$progupdatepending} {
2401         set progupdatepending 1
2402         after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2403     }
2404 }
2405
2406 proc doprogupdate {} {
2407     global lastprogupdate progupdatepending
2408
2409     if {$progupdatepending} {
2410         set progupdatepending 0
2411         set lastprogupdate [clock clicks -milliseconds]
2412         update
2413     }
2414 }
2415
2416 proc savestuff {w} {
2417     global canv canv2 canv3 mainfont textfont uifont tabstop
2418     global stuffsaved findmergefiles maxgraphpct
2419     global maxwidth showneartags showlocalchanges
2420     global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2421     global cmitmode wrapcomment datetimeformat limitdiffs
2422     global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2423     global autoselect extdifftool perfile_attrs markbgcolor
2424
2425     if {$stuffsaved} return
2426     if {![winfo viewable .]} return
2427     catch {
2428         set f [open "~/.gitk-new" w]
2429         puts $f [list set mainfont $mainfont]
2430         puts $f [list set textfont $textfont]
2431         puts $f [list set uifont $uifont]
2432         puts $f [list set tabstop $tabstop]
2433         puts $f [list set findmergefiles $findmergefiles]
2434         puts $f [list set maxgraphpct $maxgraphpct]
2435         puts $f [list set maxwidth $maxwidth]
2436         puts $f [list set cmitmode $cmitmode]
2437         puts $f [list set wrapcomment $wrapcomment]
2438         puts $f [list set autoselect $autoselect]
2439         puts $f [list set showneartags $showneartags]
2440         puts $f [list set showlocalchanges $showlocalchanges]
2441         puts $f [list set datetimeformat $datetimeformat]
2442         puts $f [list set limitdiffs $limitdiffs]
2443         puts $f [list set bgcolor $bgcolor]
2444         puts $f [list set fgcolor $fgcolor]
2445         puts $f [list set colors $colors]
2446         puts $f [list set diffcolors $diffcolors]
2447         puts $f [list set markbgcolor $markbgcolor]
2448         puts $f [list set diffcontext $diffcontext]
2449         puts $f [list set selectbgcolor $selectbgcolor]
2450         puts $f [list set extdifftool $extdifftool]
2451         puts $f [list set perfile_attrs $perfile_attrs]
2452
2453         puts $f "set geometry(main) [wm geometry .]"
2454         puts $f "set geometry(topwidth) [winfo width .tf]"
2455         puts $f "set geometry(topheight) [winfo height .tf]"
2456         puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2457         puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2458         puts $f "set geometry(botwidth) [winfo width .bleft]"
2459         puts $f "set geometry(botheight) [winfo height .bleft]"
2460
2461         puts -nonewline $f "set permviews {"
2462         for {set v 0} {$v < $nextviewnum} {incr v} {
2463             if {$viewperm($v)} {
2464                 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2465             }
2466         }
2467         puts $f "}"
2468         close $f
2469         file rename -force "~/.gitk-new" "~/.gitk"
2470     }
2471     set stuffsaved 1
2472 }
2473
2474 proc resizeclistpanes {win w} {
2475     global oldwidth
2476     if {[info exists oldwidth($win)]} {
2477         set s0 [$win sash coord 0]
2478         set s1 [$win sash coord 1]
2479         if {$w < 60} {
2480             set sash0 [expr {int($w/2 - 2)}]
2481             set sash1 [expr {int($w*5/6 - 2)}]
2482         } else {
2483             set factor [expr {1.0 * $w / $oldwidth($win)}]
2484             set sash0 [expr {int($factor * [lindex $s0 0])}]
2485             set sash1 [expr {int($factor * [lindex $s1 0])}]
2486             if {$sash0 < 30} {
2487                 set sash0 30
2488             }
2489             if {$sash1 < $sash0 + 20} {
2490                 set sash1 [expr {$sash0 + 20}]
2491             }
2492             if {$sash1 > $w - 10} {
2493                 set sash1 [expr {$w - 10}]
2494                 if {$sash0 > $sash1 - 20} {
2495                     set sash0 [expr {$sash1 - 20}]
2496                 }
2497             }
2498         }
2499         $win sash place 0 $sash0 [lindex $s0 1]
2500         $win sash place 1 $sash1 [lindex $s1 1]
2501     }
2502     set oldwidth($win) $w
2503 }
2504
2505 proc resizecdetpanes {win w} {
2506     global oldwidth
2507     if {[info exists oldwidth($win)]} {
2508         set s0 [$win sash coord 0]
2509         if {$w < 60} {
2510             set sash0 [expr {int($w*3/4 - 2)}]
2511         } else {
2512             set factor [expr {1.0 * $w / $oldwidth($win)}]
2513             set sash0 [expr {int($factor * [lindex $s0 0])}]
2514             if {$sash0 < 45} {
2515                 set sash0 45
2516             }
2517             if {$sash0 > $w - 15} {
2518                 set sash0 [expr {$w - 15}]
2519             }
2520         }
2521         $win sash place 0 $sash0 [lindex $s0 1]
2522     }
2523     set oldwidth($win) $w
2524 }
2525
2526 proc allcanvs args {
2527     global canv canv2 canv3
2528     eval $canv $args
2529     eval $canv2 $args
2530     eval $canv3 $args
2531 }
2532
2533 proc bindall {event action} {
2534     global canv canv2 canv3
2535     bind $canv $event $action
2536     bind $canv2 $event $action
2537     bind $canv3 $event $action
2538 }
2539
2540 proc about {} {
2541     global uifont
2542     set w .about
2543     if {[winfo exists $w]} {
2544         raise $w
2545         return
2546     }
2547     toplevel $w
2548     wm title $w [mc "About gitk"]
2549     wm transient $w .
2550     message $w.m -text [mc "
2551 Gitk - a commit viewer for git
2552
2553 Copyright © 2005-2008 Paul Mackerras
2554
2555 Use and redistribute under the terms of the GNU General Public License"] \
2556             -justify center -aspect 400 -border 2 -bg white -relief groove
2557     pack $w.m -side top -fill x -padx 2 -pady 2
2558     button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2559     pack $w.ok -side bottom
2560     bind $w <Visibility> "focus $w.ok"
2561     bind $w <Key-Escape> "destroy $w"
2562     bind $w <Key-Return> "destroy $w"
2563 }
2564
2565 proc keys {} {
2566     set w .keys
2567     if {[winfo exists $w]} {
2568         raise $w
2569         return
2570     }
2571     if {[tk windowingsystem] eq {aqua}} {
2572         set M1T Cmd
2573     } else {
2574         set M1T Ctrl
2575     }
2576     toplevel $w
2577     wm title $w [mc "Gitk key bindings"]
2578     wm transient $w .
2579     message $w.m -text "
2580 [mc "Gitk key bindings:"]
2581
2582 [mc "<%s-Q>             Quit" $M1T]
2583 [mc "<Home>             Move to first commit"]
2584 [mc "<End>              Move to last commit"]
2585 [mc "<Up>, p, i Move up one commit"]
2586 [mc "<Down>, n, k       Move down one commit"]
2587 [mc "<Left>, z, j       Go back in history list"]
2588 [mc "<Right>, x, l      Go forward in history list"]
2589 [mc "<PageUp>   Move up one page in commit list"]
2590 [mc "<PageDown> Move down one page in commit list"]
2591 [mc "<%s-Home>  Scroll to top of commit list" $M1T]
2592 [mc "<%s-End>   Scroll to bottom of commit list" $M1T]
2593 [mc "<%s-Up>    Scroll commit list up one line" $M1T]
2594 [mc "<%s-Down>  Scroll commit list down one line" $M1T]
2595 [mc "<%s-PageUp>        Scroll commit list up one page" $M1T]
2596 [mc "<%s-PageDown>      Scroll commit list down one page" $M1T]
2597 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2598 [mc "<Shift-Down>       Find forwards (downwards, earlier commits)"]
2599 [mc "<Delete>, b        Scroll diff view up one page"]
2600 [mc "<Backspace>        Scroll diff view up one page"]
2601 [mc "<Space>            Scroll diff view down one page"]
2602 [mc "u          Scroll diff view up 18 lines"]
2603 [mc "d          Scroll diff view down 18 lines"]
2604 [mc "<%s-F>             Find" $M1T]
2605 [mc "<%s-G>             Move to next find hit" $M1T]
2606 [mc "<Return>   Move to next find hit"]
2607 [mc "/          Move to next find hit, or redo find"]
2608 [mc "?          Move to previous find hit"]
2609 [mc "f          Scroll diff view to next file"]
2610 [mc "<%s-S>             Search for next hit in diff view" $M1T]
2611 [mc "<%s-R>             Search for previous hit in diff view" $M1T]
2612 [mc "<%s-KP+>   Increase font size" $M1T]
2613 [mc "<%s-plus>  Increase font size" $M1T]
2614 [mc "<%s-KP->   Decrease font size" $M1T]
2615 [mc "<%s-minus> Decrease font size" $M1T]
2616 [mc "<F5>               Update"]
2617 " \
2618             -justify left -bg white -border 2 -relief groove
2619     pack $w.m -side top -fill both -padx 2 -pady 2
2620     button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2621     bind $w <Key-Escape> [list destroy $w]
2622     pack $w.ok -side bottom
2623     bind $w <Visibility> "focus $w.ok"
2624     bind $w <Key-Escape> "destroy $w"
2625     bind $w <Key-Return> "destroy $w"
2626 }
2627
2628 # Procedures for manipulating the file list window at the
2629 # bottom right of the overall window.
2630
2631 proc treeview {w l openlevs} {
2632     global treecontents treediropen treeheight treeparent treeindex
2633
2634     set ix 0
2635     set treeindex() 0
2636     set lev 0
2637     set prefix {}
2638     set prefixend -1
2639     set prefendstack {}
2640     set htstack {}
2641     set ht 0
2642     set treecontents() {}
2643     $w conf -state normal
2644     foreach f $l {
2645         while {[string range $f 0 $prefixend] ne $prefix} {
2646             if {$lev <= $openlevs} {
2647                 $w mark set e:$treeindex($prefix) "end -1c"
2648                 $w mark gravity e:$treeindex($prefix) left
2649             }
2650             set treeheight($prefix) $ht
2651             incr ht [lindex $htstack end]
2652             set htstack [lreplace $htstack end end]
2653             set prefixend [lindex $prefendstack end]
2654             set prefendstack [lreplace $prefendstack end end]
2655             set prefix [string range $prefix 0 $prefixend]
2656             incr lev -1
2657         }
2658         set tail [string range $f [expr {$prefixend+1}] end]
2659         while {[set slash [string first "/" $tail]] >= 0} {
2660             lappend htstack $ht
2661             set ht 0
2662             lappend prefendstack $prefixend
2663             incr prefixend [expr {$slash + 1}]
2664             set d [string range $tail 0 $slash]
2665             lappend treecontents($prefix) $d
2666             set oldprefix $prefix
2667             append prefix $d
2668             set treecontents($prefix) {}
2669             set treeindex($prefix) [incr ix]
2670             set treeparent($prefix) $oldprefix
2671             set tail [string range $tail [expr {$slash+1}] end]
2672             if {$lev <= $openlevs} {
2673                 set ht 1
2674                 set treediropen($prefix) [expr {$lev < $openlevs}]
2675                 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2676                 $w mark set d:$ix "end -1c"
2677                 $w mark gravity d:$ix left
2678                 set str "\n"
2679                 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2680                 $w insert end $str
2681                 $w image create end -align center -image $bm -padx 1 \
2682                     -name a:$ix
2683                 $w insert end $d [highlight_tag $prefix]
2684                 $w mark set s:$ix "end -1c"
2685                 $w mark gravity s:$ix left
2686             }
2687             incr lev
2688         }
2689         if {$tail ne {}} {
2690             if {$lev <= $openlevs} {
2691                 incr ht
2692                 set str "\n"
2693                 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2694                 $w insert end $str
2695                 $w insert end $tail [highlight_tag $f]
2696             }
2697             lappend treecontents($prefix) $tail
2698         }
2699     }
2700     while {$htstack ne {}} {
2701         set treeheight($prefix) $ht
2702         incr ht [lindex $htstack end]
2703         set htstack [lreplace $htstack end end]
2704         set prefixend [lindex $prefendstack end]
2705         set prefendstack [lreplace $prefendstack end end]
2706         set prefix [string range $prefix 0 $prefixend]
2707     }
2708     $w conf -state disabled
2709 }
2710
2711 proc linetoelt {l} {
2712     global treeheight treecontents
2713
2714     set y 2
2715     set prefix {}
2716     while {1} {
2717         foreach e $treecontents($prefix) {
2718             if {$y == $l} {
2719                 return "$prefix$e"
2720             }
2721             set n 1
2722             if {[string index $e end] eq "/"} {
2723                 set n $treeheight($prefix$e)
2724                 if {$y + $n > $l} {
2725                     append prefix $e
2726                     incr y
2727                     break
2728                 }
2729             }
2730             incr y $n
2731         }
2732     }
2733 }
2734
2735 proc highlight_tree {y prefix} {
2736     global treeheight treecontents cflist
2737
2738     foreach e $treecontents($prefix) {
2739         set path $prefix$e
2740         if {[highlight_tag $path] ne {}} {
2741             $cflist tag add bold $y.0 "$y.0 lineend"
2742         }
2743         incr y
2744         if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2745             set y [highlight_tree $y $path]
2746         }
2747     }
2748     return $y
2749 }
2750
2751 proc treeclosedir {w dir} {
2752     global treediropen treeheight treeparent treeindex
2753
2754     set ix $treeindex($dir)
2755     $w conf -state normal
2756     $w delete s:$ix e:$ix
2757     set treediropen($dir) 0
2758     $w image configure a:$ix -image tri-rt
2759     $w conf -state disabled
2760     set n [expr {1 - $treeheight($dir)}]
2761     while {$dir ne {}} {
2762         incr treeheight($dir) $n
2763         set dir $treeparent($dir)
2764     }
2765 }
2766
2767 proc treeopendir {w dir} {
2768     global treediropen treeheight treeparent treecontents treeindex
2769
2770     set ix $treeindex($dir)
2771     $w conf -state normal
2772     $w image configure a:$ix -image tri-dn
2773     $w mark set e:$ix s:$ix
2774     $w mark gravity e:$ix right
2775     set lev 0
2776     set str "\n"
2777     set n [llength $treecontents($dir)]
2778     for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2779         incr lev
2780         append str "\t"
2781         incr treeheight($x) $n
2782     }
2783     foreach e $treecontents($dir) {
2784         set de $dir$e
2785         if {[string index $e end] eq "/"} {
2786             set iy $treeindex($de)
2787             $w mark set d:$iy e:$ix
2788             $w mark gravity d:$iy left
2789             $w insert e:$ix $str
2790             set treediropen($de) 0
2791             $w image create e:$ix -align center -image tri-rt -padx 1 \
2792                 -name a:$iy
2793             $w insert e:$ix $e [highlight_tag $de]
2794             $w mark set s:$iy e:$ix
2795             $w mark gravity s:$iy left
2796             set treeheight($de) 1
2797         } else {
2798             $w insert e:$ix $str
2799             $w insert e:$ix $e [highlight_tag $de]
2800         }
2801     }
2802     $w mark gravity e:$ix right
2803     $w conf -state disabled
2804     set treediropen($dir) 1
2805     set top [lindex [split [$w index @0,0] .] 0]
2806     set ht [$w cget -height]
2807     set l [lindex [split [$w index s:$ix] .] 0]
2808     if {$l < $top} {
2809         $w yview $l.0
2810     } elseif {$l + $n + 1 > $top + $ht} {
2811         set top [expr {$l + $n + 2 - $ht}]
2812         if {$l < $top} {
2813             set top $l
2814         }
2815         $w yview $top.0
2816     }
2817 }
2818
2819 proc treeclick {w x y} {
2820     global treediropen cmitmode ctext cflist cflist_top
2821
2822     if {$cmitmode ne "tree"} return
2823     if {![info exists cflist_top]} return
2824     set l [lindex [split [$w index "@$x,$y"] "."] 0]
2825     $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2826     $cflist tag add highlight $l.0 "$l.0 lineend"
2827     set cflist_top $l
2828     if {$l == 1} {
2829         $ctext yview 1.0
2830         return
2831     }
2832     set e [linetoelt $l]
2833     if {[string index $e end] ne "/"} {
2834         showfile $e
2835     } elseif {$treediropen($e)} {
2836         treeclosedir $w $e
2837     } else {
2838         treeopendir $w $e
2839     }
2840 }
2841
2842 proc setfilelist {id} {
2843     global treefilelist cflist jump_to_here
2844
2845     treeview $cflist $treefilelist($id) 0
2846     if {$jump_to_here ne {}} {
2847         set f [lindex $jump_to_here 0]
2848         if {[lsearch -exact $treefilelist($id) $f] >= 0} {
2849             showfile $f
2850         }
2851     }
2852 }
2853
2854 image create bitmap tri-rt -background black -foreground blue -data {
2855     #define tri-rt_width 13
2856     #define tri-rt_height 13
2857     static unsigned char tri-rt_bits[] = {
2858        0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2859        0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2860        0x00, 0x00};
2861 } -maskdata {
2862     #define tri-rt-mask_width 13
2863     #define tri-rt-mask_height 13
2864     static unsigned char tri-rt-mask_bits[] = {
2865        0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2866        0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2867        0x08, 0x00};
2868 }
2869 image create bitmap tri-dn -background black -foreground blue -data {
2870     #define tri-dn_width 13
2871     #define tri-dn_height 13
2872     static unsigned char tri-dn_bits[] = {
2873        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2874        0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2875        0x00, 0x00};
2876 } -maskdata {
2877     #define tri-dn-mask_width 13
2878     #define tri-dn-mask_height 13
2879     static unsigned char tri-dn-mask_bits[] = {
2880        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2881        0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2882        0x00, 0x00};
2883 }
2884
2885 image create bitmap reficon-T -background black -foreground yellow -data {
2886     #define tagicon_width 13
2887     #define tagicon_height 9
2888     static unsigned char tagicon_bits[] = {
2889        0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2890        0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2891 } -maskdata {
2892     #define tagicon-mask_width 13
2893     #define tagicon-mask_height 9
2894     static unsigned char tagicon-mask_bits[] = {
2895        0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2896        0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2897 }
2898 set rectdata {
2899     #define headicon_width 13
2900     #define headicon_height 9
2901     static unsigned char headicon_bits[] = {
2902        0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2903        0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2904 }
2905 set rectmask {
2906     #define headicon-mask_width 13
2907     #define headicon-mask_height 9
2908     static unsigned char headicon-mask_bits[] = {
2909        0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2910        0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2911 }
2912 image create bitmap reficon-H -background black -foreground green \
2913     -data $rectdata -maskdata $rectmask
2914 image create bitmap reficon-o -background black -foreground "#ddddff" \
2915     -data $rectdata -maskdata $rectmask
2916
2917 proc init_flist {first} {
2918     global cflist cflist_top difffilestart
2919
2920     $cflist conf -state normal
2921     $cflist delete 0.0 end
2922     if {$first ne {}} {
2923         $cflist insert end $first
2924         set cflist_top 1
2925         $cflist tag add highlight 1.0 "1.0 lineend"
2926     } else {
2927         catch {unset cflist_top}
2928     }
2929     $cflist conf -state disabled
2930     set difffilestart {}
2931 }
2932
2933 proc highlight_tag {f} {
2934     global highlight_paths
2935
2936     foreach p $highlight_paths {
2937         if {[string match $p $f]} {
2938             return "bold"
2939         }
2940     }
2941     return {}
2942 }
2943
2944 proc highlight_filelist {} {
2945     global cmitmode cflist
2946
2947     $cflist conf -state normal
2948     if {$cmitmode ne "tree"} {
2949         set end [lindex [split [$cflist index end] .] 0]
2950         for {set l 2} {$l < $end} {incr l} {
2951             set line [$cflist get $l.0 "$l.0 lineend"]
2952             if {[highlight_tag $line] ne {}} {
2953                 $cflist tag add bold $l.0 "$l.0 lineend"
2954             }
2955         }
2956     } else {
2957         highlight_tree 2 {}
2958     }
2959     $cflist conf -state disabled
2960 }
2961
2962 proc unhighlight_filelist {} {
2963     global cflist
2964
2965     $cflist conf -state normal
2966     $cflist tag remove bold 1.0 end
2967     $cflist conf -state disabled
2968 }
2969
2970 proc add_flist {fl} {
2971     global cflist
2972
2973     $cflist conf -state normal
2974     foreach f $fl {
2975         $cflist insert end "\n"
2976         $cflist insert end $f [highlight_tag $f]
2977     }
2978     $cflist conf -state disabled
2979 }
2980
2981 proc sel_flist {w x y} {
2982     global ctext difffilestart cflist cflist_top cmitmode
2983
2984     if {$cmitmode eq "tree"} return
2985     if {![info exists cflist_top]} return
2986     set l [lindex [split [$w index "@$x,$y"] "."] 0]
2987     $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2988     $cflist tag add highlight $l.0 "$l.0 lineend"
2989     set cflist_top $l
2990     if {$l == 1} {
2991         $ctext yview 1.0
2992     } else {
2993         catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2994     }
2995 }
2996
2997 proc pop_flist_menu {w X Y x y} {
2998     global ctext cflist cmitmode flist_menu flist_menu_file
2999     global treediffs diffids
3000
3001     stopfinding
3002     set l [lindex [split [$w index "@$x,$y"] "."] 0]
3003     if {$l <= 1} return
3004     if {$cmitmode eq "tree"} {
3005         set e [linetoelt $l]
3006         if {[string index $e end] eq "/"} return
3007     } else {
3008         set e [lindex $treediffs($diffids) [expr {$l-2}]]
3009     }
3010     set flist_menu_file $e
3011     set xdiffstate "normal"
3012     if {$cmitmode eq "tree"} {
3013         set xdiffstate "disabled"
3014     }
3015     # Disable "External diff" item in tree mode
3016     $flist_menu entryconf 2 -state $xdiffstate
3017     tk_popup $flist_menu $X $Y
3018 }
3019
3020 proc find_ctext_fileinfo {line} {
3021     global ctext_file_names ctext_file_lines
3022
3023     set ok [bsearch $ctext_file_lines $line]
3024     set tline [lindex $ctext_file_lines $ok]
3025
3026     if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3027         return {}
3028     } else {
3029         return [list [lindex $ctext_file_names $ok] $tline]
3030     }
3031 }
3032
3033 proc pop_diff_menu {w X Y x y} {
3034     global ctext diff_menu flist_menu_file
3035     global diff_menu_txtpos diff_menu_line
3036     global diff_menu_filebase
3037
3038     set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3039     set diff_menu_line [lindex $diff_menu_txtpos 0]
3040     # don't pop up the menu on hunk-separator or file-separator lines
3041     if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3042         return
3043     }
3044     stopfinding
3045     set f [find_ctext_fileinfo $diff_menu_line]
3046     if {$f eq {}} return
3047     set flist_menu_file [lindex $f 0]
3048     set diff_menu_filebase [lindex $f 1]
3049     tk_popup $diff_menu $X $Y
3050 }
3051
3052 proc flist_hl {only} {
3053     global flist_menu_file findstring gdttype
3054
3055     set x [shellquote $flist_menu_file]
3056     if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3057         set findstring $x
3058     } else {
3059         append findstring " " $x
3060     }
3061     set gdttype [mc "touching paths:"]
3062 }
3063
3064 proc save_file_from_commit {filename output what} {
3065     global nullfile
3066
3067     if {[catch {exec git show $filename -- > $output} err]} {
3068         if {[string match "fatal: bad revision *" $err]} {
3069             return $nullfile
3070         }
3071         error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3072         return {}
3073     }
3074     return $output
3075 }
3076
3077 proc external_diff_get_one_file {diffid filename diffdir} {
3078     global nullid nullid2 nullfile
3079     global gitdir
3080
3081     if {$diffid == $nullid} {
3082         set difffile [file join [file dirname $gitdir] $filename]
3083         if {[file exists $difffile]} {
3084             return $difffile
3085         }
3086         return $nullfile
3087     }
3088     if {$diffid == $nullid2} {
3089         set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3090         return [save_file_from_commit :$filename $difffile index]
3091     }
3092     set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3093     return [save_file_from_commit $diffid:$filename $difffile \
3094                "revision $diffid"]
3095 }
3096
3097 proc external_diff {} {
3098     global gitktmpdir nullid nullid2
3099     global flist_menu_file
3100     global diffids
3101     global diffnum
3102     global gitdir extdifftool
3103
3104     if {[llength $diffids] == 1} {
3105         # no reference commit given
3106         set diffidto [lindex $diffids 0]
3107         if {$diffidto eq $nullid} {
3108             # diffing working copy with index
3109             set diffidfrom $nullid2
3110         } elseif {$diffidto eq $nullid2} {
3111             # diffing index with HEAD
3112             set diffidfrom "HEAD"
3113         } else {
3114             # use first parent commit
3115             global parentlist selectedline
3116             set diffidfrom [lindex $parentlist $selectedline 0]
3117         }
3118     } else {
3119         set diffidfrom [lindex $diffids 0]
3120         set diffidto [lindex $diffids 1]
3121     }
3122
3123     # make sure that several diffs wont collide
3124     if {![info exists gitktmpdir]} {
3125         set gitktmpdir [file join [file dirname $gitdir] \
3126                             [format ".gitk-tmp.%s" [pid]]]
3127         if {[catch {file mkdir $gitktmpdir} err]} {
3128             error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3129             unset gitktmpdir
3130             return
3131         }
3132         set diffnum 0
3133     }
3134     incr diffnum
3135     set diffdir [file join $gitktmpdir $diffnum]
3136     if {[catch {file mkdir $diffdir} err]} {
3137         error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3138         return
3139     }
3140
3141     # gather files to diff
3142     set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3143     set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3144
3145     if {$difffromfile ne {} && $difftofile ne {}} {
3146         set cmd [concat | [shellsplit $extdifftool] \
3147                      [list $difffromfile $difftofile]]
3148         if {[catch {set fl [open $cmd r]} err]} {
3149             file delete -force $diffdir
3150             error_popup "$extdifftool: [mc "command failed:"] $err"
3151         } else {
3152             fconfigure $fl -blocking 0
3153             filerun $fl [list delete_at_eof $fl $diffdir]
3154         }
3155     }
3156 }
3157
3158 proc find_hunk_blamespec {base line} {
3159     global ctext
3160
3161     # Find and parse the hunk header
3162     set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3163     if {$s_lix eq {}} return
3164
3165     set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3166     if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3167             s_line old_specs osz osz1 new_line nsz]} {
3168         return
3169     }
3170
3171     # base lines for the parents
3172     set base_lines [list $new_line]
3173     foreach old_spec [lrange [split $old_specs " "] 1 end] {
3174         if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3175                 old_spec old_line osz]} {
3176             return
3177         }
3178         lappend base_lines $old_line
3179     }
3180
3181     # Now scan the lines to determine offset within the hunk
3182     set max_parent [expr {[llength $base_lines]-2}]
3183     set dline 0
3184     set s_lno [lindex [split $s_lix "."] 0]
3185
3186     # Determine if the line is removed
3187     set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3188     if {[string match {[-+ ]*} $chunk]} {
3189         set removed_idx [string first "-" $chunk]
3190         # Choose a parent index
3191         if {$removed_idx >= 0} {
3192             set parent $removed_idx
3193         } else {
3194             set unchanged_idx [string first " " $chunk]
3195             if {$unchanged_idx >= 0} {
3196                 set parent $unchanged_idx
3197             } else {
3198                 # blame the current commit
3199                 set parent -1
3200             }
3201         }
3202         # then count other lines that belong to it
3203         for {set i $line} {[incr i -1] > $s_lno} {} {
3204             set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3205             # Determine if the line is removed
3206             set removed_idx [string first "-" $chunk]
3207             if {$parent >= 0} {
3208                 set code [string index $chunk $parent]
3209                 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3210                     incr dline
3211                 }
3212             } else {
3213                 if {$removed_idx < 0} {
3214                     incr dline
3215                 }
3216             }
3217         }
3218         incr parent
3219     } else {
3220         set parent 0
3221     }
3222
3223     incr dline [lindex $base_lines $parent]
3224     return [list $parent $dline]
3225 }
3226
3227 proc external_blame_diff {} {
3228     global currentid cmitmode
3229     global diff_menu_txtpos diff_menu_line
3230     global diff_menu_filebase flist_menu_file
3231
3232     if {$cmitmode eq "tree"} {
3233         set parent_idx 0
3234         set line [expr {$diff_menu_line - $diff_menu_filebase}]
3235     } else {
3236         set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3237         if {$hinfo ne {}} {
3238             set parent_idx [lindex $hinfo 0]
3239             set line [lindex $hinfo 1]
3240         } else {
3241             set parent_idx 0
3242             set line 0
3243         }
3244     }
3245
3246     external_blame $parent_idx $line
3247 }
3248
3249 # Find the SHA1 ID of the blob for file $fname in the index
3250 # at stage 0 or 2
3251 proc index_sha1 {fname} {
3252     set f [open [list | git ls-files -s $fname] r]
3253     while {[gets $f line] >= 0} {
3254         set info [lindex [split $line "\t"] 0]
3255         set stage [lindex $info 2]
3256         if {$stage eq "0" || $stage eq "2"} {
3257             close $f
3258             return [lindex $info 1]
3259         }
3260     }
3261     close $f
3262     return {}
3263 }
3264
3265 proc external_blame {parent_idx {line {}}} {
3266     global flist_menu_file
3267     global nullid nullid2
3268     global parentlist selectedline currentid
3269
3270     if {$parent_idx > 0} {
3271         set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3272     } else {
3273         set base_commit $currentid
3274     }
3275
3276     if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3277         error_popup [mc "No such commit"]
3278         return
3279     }
3280
3281     set cmdline [list git gui blame]
3282     if {$line ne {} && $line > 1} {
3283         lappend cmdline "--line=$line"
3284     }
3285     lappend cmdline $base_commit $flist_menu_file
3286     if {[catch {eval exec $cmdline &} err]} {
3287         error_popup "[mc "git gui blame: command failed:"] $err"
3288     }
3289 }
3290
3291 proc show_line_source {} {
3292     global cmitmode currentid parents curview blamestuff blameinst
3293     global diff_menu_line diff_menu_filebase flist_menu_file
3294     global nullid nullid2 gitdir
3295
3296     set from_index {}
3297     if {$cmitmode eq "tree"} {
3298         set id $currentid
3299         set line [expr {$diff_menu_line - $diff_menu_filebase}]
3300     } else {
3301         set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3302         if {$h eq {}} return
3303         set pi [lindex $h 0]
3304         if {$pi == 0} {
3305             mark_ctext_line $diff_menu_line
3306             return
3307         }
3308         incr pi -1
3309         if {$currentid eq $nullid} {
3310             if {$pi > 0} {
3311                 # must be a merge in progress...
3312                 if {[catch {
3313                     # get the last line from .git/MERGE_HEAD
3314                     set f [open [file join $gitdir MERGE_HEAD] r]
3315                     set id [lindex [split [read $f] "\n"] end-1]
3316                     close $f
3317                 } err]} {
3318                     error_popup [mc "Couldn't read merge head: %s" $err]
3319                     return
3320                 }
3321             } elseif {$parents($curview,$currentid) eq $nullid2} {
3322                 # need to do the blame from the index
3323                 if {[catch {
3324                     set from_index [index_sha1 $flist_menu_file]
3325                 } err]} {
3326                     error_popup [mc "Error reading index: %s" $err]
3327                     return
3328                 }
3329             }
3330         } else {
3331             set id [lindex $parents($curview,$currentid) $pi]
3332         }
3333         set line [lindex $h 1]
3334     }
3335     set blameargs {}
3336     if {$from_index ne {}} {
3337         lappend blameargs | git cat-file blob $from_index
3338     }
3339     lappend blameargs | git blame -p -L$line,+1
3340     if {$from_index ne {}} {
3341         lappend blameargs --contents -
3342     } else {
3343         lappend blameargs $id
3344     }
3345     lappend blameargs -- $flist_menu_file
3346     if {[catch {
3347         set f [open $blameargs r]
3348     } err]} {
3349         error_popup [mc "Couldn't start git blame: %s" $err]
3350         return
3351     }
3352     fconfigure $f -blocking 0
3353     set i [reg_instance $f]
3354     set blamestuff($i) {}
3355     set blameinst $i
3356     filerun $f [list read_line_source $f $i]
3357 }
3358
3359 proc stopblaming {} {
3360     global blameinst
3361
3362     if {[info exists blameinst]} {
3363         stop_instance $blameinst
3364         unset blameinst
3365     }
3366 }
3367
3368 proc read_line_source {fd inst} {
3369     global blamestuff curview commfd blameinst nullid nullid2
3370
3371     while {[gets $fd line] >= 0} {
3372         lappend blamestuff($inst) $line
3373     }
3374     if {![eof $fd]} {
3375         return 1
3376     }
3377     unset commfd($inst)
3378     unset blameinst
3379     fconfigure $fd -blocking 1
3380     if {[catch {close $fd} err]} {
3381         error_popup [mc "Error running git blame: %s" $err]
3382         return 0
3383     }
3384
3385     set fname {}
3386     set line [split [lindex $blamestuff($inst) 0] " "]
3387     set id [lindex $line 0]
3388     set lnum [lindex $line 1]
3389     if {[string length $id] == 40 && [string is xdigit $id] &&
3390         [string is digit -strict $lnum]} {
3391         # look for "filename" line
3392         foreach l $blamestuff($inst) {
3393             if {[string match "filename *" $l]} {
3394                 set fname [string range $l 9 end]
3395                 break
3396             }
3397         }
3398     }
3399     if {$fname ne {}} {
3400         # all looks good, select it
3401         if {$id eq $nullid} {
3402             # blame uses all-zeroes to mean not committed,
3403             # which would mean a change in the index
3404             set id $nullid2
3405         }
3406         if {[commitinview $id $curview]} {
3407             selectline [rowofcommit $id] 1 [list $fname $lnum]
3408         } else {
3409             error_popup [mc "That line comes from commit %s, \
3410                              which is not in this view" [shortids $id]]
3411         }
3412     } else {
3413         puts "oops couldn't parse git blame output"
3414     }
3415     return 0
3416 }
3417
3418 # delete $dir when we see eof on $f (presumably because the child has exited)
3419 proc delete_at_eof {f dir} {
3420     while {[gets $f line] >= 0} {}
3421     if {[eof $f]} {
3422         if {[catch {close $f} err]} {
3423             error_popup "[mc "External diff viewer failed:"] $err"
3424         }
3425         file delete -force $dir
3426         return 0
3427     }
3428     return 1
3429 }
3430
3431 # Functions for adding and removing shell-type quoting
3432
3433 proc shellquote {str} {
3434     if {![string match "*\['\"\\ \t]*" $str]} {
3435         return $str
3436     }
3437     if {![string match "*\['\"\\]*" $str]} {
3438         return "\"$str\""
3439     }
3440     if {![string match "*'*" $str]} {
3441         return "'$str'"
3442     }
3443     return "\"[string map {\" \\\" \\ \\\\} $str]\""
3444 }
3445
3446 proc shellarglist {l} {
3447     set str {}
3448     foreach a $l {
3449         if {$str ne {}} {
3450             append str " "
3451         }
3452         append str [shellquote $a]
3453     }
3454     return $str
3455 }
3456
3457 proc shelldequote {str} {
3458     set ret {}
3459     set used -1
3460     while {1} {
3461         incr used
3462         if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3463             append ret [string range $str $used end]
3464             set used [string length $str]
3465             break
3466         }
3467         set first [lindex $first 0]
3468         set ch [string index $str $first]
3469         if {$first > $used} {
3470             append ret [string range $str $used [expr {$first - 1}]]
3471             set used $first
3472         }
3473         if {$ch eq " " || $ch eq "\t"} break
3474         incr used
3475         if {$ch eq "'"} {
3476             set first [string first "'" $str $used]
3477             if {$first < 0} {
3478                 error "unmatched single-quote"
3479             }
3480             append ret [string range $str $used [expr {$first - 1}]]
3481             set used $first
3482             continue
3483         }
3484         if {$ch eq "\\"} {
3485             if {$used >= [string length $str]} {
3486                 error "trailing backslash"
3487             }
3488             append ret [string index $str $used]
3489             continue
3490         }
3491         # here ch == "\""
3492         while {1} {
3493             if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3494                 error "unmatched double-quote"
3495             }
3496             set first [lindex $first 0]
3497             set ch [string index $str $first]
3498             if {$first > $used} {
3499                 append ret [string range $str $used [expr {$first - 1}]]
3500                 set used $first
3501             }
3502             if {$ch eq "\""} break
3503             incr used
3504             append ret [string index $str $used]
3505             incr used
3506         }
3507     }
3508     return [list $used $ret]
3509 }
3510
3511 proc shellsplit {str} {
3512     set l {}
3513     while {1} {
3514         set str [string trimleft $str]
3515         if {$str eq {}} break
3516         set dq [shelldequote $str]
3517         set n [lindex $dq 0]
3518         set word [lindex $dq 1]
3519         set str [string range $str $n end]
3520         lappend l $word
3521     }
3522     return $l
3523 }
3524
3525 # Code to implement multiple views
3526
3527 proc newview {ishighlight} {
3528     global nextviewnum newviewname newishighlight
3529     global revtreeargs viewargscmd newviewopts curview
3530
3531     set newishighlight $ishighlight
3532     set top .gitkview
3533     if {[winfo exists $top]} {
3534         raise $top
3535         return
3536     }
3537     set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3538     set newviewopts($nextviewnum,perm) 0
3539     set newviewopts($nextviewnum,cmd)  $viewargscmd($curview)
3540     decode_view_opts $nextviewnum $revtreeargs
3541     vieweditor $top $nextviewnum [mc "Gitk view definition"]
3542 }
3543
3544 set known_view_options {
3545     {perm    b    . {}               {mc "Remember this view"}}
3546     {args    t50= + {}               {mc "Commits to include (arguments to git log):"}}
3547     {all     b    * "--all"          {mc "Use all refs"}}
3548     {dorder  b    . {"--date-order" "-d"}      {mc "Strictly sort by date"}}
3549     {lright  b    . "--left-right"   {mc "Mark branch sides"}}
3550     {since   t15  + {"--since=*" "--after=*"}  {mc "Since date:"}}
3551     {until   t15  . {"--until=*" "--before=*"} {mc "Until date:"}}
3552     {limit   t10  + "--max-count=*"  {mc "Max count:"}}
3553     {skip    t10  . "--skip=*"       {mc "Skip:"}}
3554     {first   b    . "--first-parent" {mc "Limit to first parent"}}
3555     {cmd     t50= + {}               {mc "Command to generate more commits to include:"}}
3556     }
3557
3558 proc encode_view_opts {n} {
3559     global known_view_options newviewopts
3560
3561     set rargs [list]
3562     foreach opt $known_view_options {
3563         set patterns [lindex $opt 3]
3564         if {$patterns eq {}} continue
3565         set pattern [lindex $patterns 0]
3566
3567         set val $newviewopts($n,[lindex $opt 0])
3568         
3569         if {[lindex $opt 1] eq "b"} {
3570             if {$val} {
3571                 lappend rargs $pattern
3572             }
3573         } else {
3574             set val [string trim $val]
3575             if {$val ne {}} {
3576                 set pfix [string range $pattern 0 end-1]
3577                 lappend rargs $pfix$val
3578             }
3579         }
3580     }
3581     return [concat $rargs [shellsplit $newviewopts($n,args)]]
3582 }
3583
3584 proc decode_view_opts {n view_args} {
3585     global known_view_options newviewopts
3586
3587     foreach opt $known_view_options {
3588         if {[lindex $opt 1] eq "b"} {
3589             set val 0
3590         } else {
3591             set val {}
3592         }
3593         set newviewopts($n,[lindex $opt 0]) $val
3594     }
3595     set oargs [list]
3596     foreach arg $view_args {
3597         if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3598             && ![info exists found(limit)]} {
3599             set newviewopts($n,limit) $cnt
3600             set found(limit) 1
3601             continue
3602         }
3603         catch { unset val }
3604         foreach opt $known_view_options {
3605             set id [lindex $opt 0]
3606             if {[info exists found($id)]} continue
3607             foreach pattern [lindex $opt 3] {
3608                 if {![string match $pattern $arg]} continue
3609                 if {[lindex $opt 1] ne "b"} {
3610                     set size [string length $pattern]
3611                     set val [string range $arg [expr {$size-1}] end]
3612                 } else {
3613                     set val 1
3614                 }
3615                 set newviewopts($n,$id) $val
3616                 set found($id) 1
3617                 break
3618             }
3619             if {[info exists val]} break
3620         }
3621         if {[info exists val]} continue
3622         lappend oargs $arg
3623     }
3624     set newviewopts($n,args) [shellarglist $oargs]
3625 }
3626
3627 proc editview {} {
3628     global curview
3629     global viewname viewperm newviewname newviewopts
3630     global viewargs viewargscmd
3631
3632     set top .gitkvedit-$curview
3633     if {[winfo exists $top]} {
3634         raise $top
3635         return
3636     }
3637     set newviewname($curview)      $viewname($curview)
3638     set newviewopts($curview,perm) $viewperm($curview)
3639     set newviewopts($curview,cmd)  $viewargscmd($curview)
3640     decode_view_opts $curview $viewargs($curview)
3641     vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3642 }
3643
3644 proc vieweditor {top n title} {
3645     global newviewname newviewopts viewfiles bgcolor
3646     global known_view_options
3647
3648     toplevel $top
3649     wm title $top $title
3650     wm transient $top .
3651
3652     # View name
3653     frame $top.nfr
3654     label $top.nl -text [mc "Name"]
3655     entry $top.name -width 20 -textvariable newviewname($n)
3656     pack $top.nfr -in $top -fill x -pady 5 -padx 3
3657     pack $top.nl -in $top.nfr -side left -padx {0 30}
3658     pack $top.name -in $top.nfr -side left
3659
3660     # View options
3661     set cframe $top.nfr
3662     set cexpand 0
3663     set cnt 0
3664     foreach opt $known_view_options {
3665         set id [lindex $opt 0]
3666         set type [lindex $opt 1]
3667         set flags [lindex $opt 2]
3668         set title [eval [lindex $opt 4]]
3669         set lxpad 0
3670
3671         if {$flags eq "+" || $flags eq "*"} {
3672             set cframe $top.fr$cnt
3673             incr cnt
3674             frame $cframe
3675             pack $cframe -in $top -fill x -pady 3 -padx 3
3676             set cexpand [expr {$flags eq "*"}]
3677         } else {
3678             set lxpad 5
3679         }
3680
3681         if {$type eq "b"} {
3682             checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
3683             pack $cframe.c_$id -in $cframe -side left \
3684                 -padx [list $lxpad 0] -expand $cexpand -anchor w
3685         } elseif {[regexp {^t(\d+)$} $type type sz]} {
3686             message $cframe.l_$id -aspect 1500 -text $title
3687             entry $cframe.e_$id -width $sz -background $bgcolor \
3688                 -textvariable newviewopts($n,$id)
3689             pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
3690             pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
3691         } elseif {[regexp {^t(\d+)=$} $type type sz]} {
3692             message $cframe.l_$id -aspect 1500 -text $title
3693             entry $cframe.e_$id -width $sz -background $bgcolor \
3694                 -textvariable newviewopts($n,$id)
3695             pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
3696             pack $cframe.e_$id -in $cframe -side top -fill x
3697         }
3698     }
3699
3700     # Path list
3701     message $top.l -aspect 1500 \
3702         -text [mc "Enter files and directories to include, one per line:"]
3703     pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3
3704     text $top.t -width 40 -height 5 -background $bgcolor -font uifont
3705     if {[info exists viewfiles($n)]} {
3706         foreach f $viewfiles($n) {
3707             $top.t insert end $f
3708             $top.t insert end "\n"
3709         }
3710         $top.t delete {end - 1c} end
3711         $top.t mark set insert 0.0
3712     }
3713     pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
3714     frame $top.buts
3715     button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3716     button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
3717     button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3718     bind $top <Control-Return> [list newviewok $top $n]
3719     bind $top <F5> [list newviewok $top $n 1]
3720     bind $top <Escape> [list destroy $top]
3721     grid $top.buts.ok $top.buts.apply $top.buts.can
3722     grid columnconfigure $top.buts 0 -weight 1 -uniform a
3723     grid columnconfigure $top.buts 1 -weight 1 -uniform a
3724     grid columnconfigure $top.buts 2 -weight 1 -uniform a
3725     pack $top.buts -in $top -side top -fill x
3726     focus $top.t
3727 }
3728
3729 proc doviewmenu {m first cmd op argv} {
3730     set nmenu [$m index end]
3731     for {set i $first} {$i <= $nmenu} {incr i} {
3732         if {[$m entrycget $i -command] eq $cmd} {
3733             eval $m $op $i $argv
3734             break
3735         }
3736     }
3737 }
3738
3739 proc allviewmenus {n op args} {
3740     # global viewhlmenu
3741
3742     doviewmenu .bar.view 5 [list showview $n] $op $args
3743     # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3744 }
3745
3746 proc newviewok {top n {apply 0}} {
3747     global nextviewnum newviewperm newviewname newishighlight
3748     global viewname viewfiles viewperm selectedview curview
3749     global viewargs viewargscmd newviewopts viewhlmenu
3750
3751     if {[catch {
3752         set newargs [encode_view_opts $n]
3753     } err]} {
3754         error_popup "[mc "Error in commit selection arguments:"] $err" $top
3755         return
3756     }
3757     set files {}
3758     foreach f [split [$top.t get 0.0 end] "\n"] {
3759         set ft [string trim $f]
3760         if {$ft ne {}} {
3761             lappend files $ft
3762         }
3763     }
3764     if {![info exists viewfiles($n)]} {
3765         # creating a new view
3766         incr nextviewnum
3767         set viewname($n) $newviewname($n)
3768         set viewperm($n) $newviewopts($n,perm)
3769         set viewfiles($n) $files
3770         set viewargs($n) $newargs
3771         set viewargscmd($n) $newviewopts($n,cmd)
3772         addviewmenu $n
3773         if {!$newishighlight} {
3774             run showview $n
3775         } else {
3776             run addvhighlight $n
3777         }
3778     } else {
3779         # editing an existing view
3780         set viewperm($n) $newviewopts($n,perm)
3781         if {$newviewname($n) ne $viewname($n)} {
3782             set viewname($n) $newviewname($n)
3783             doviewmenu .bar.view 5 [list showview $n] \
3784                 entryconf [list -label $viewname($n)]
3785             # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3786                 # entryconf [list -label $viewname($n) -value $viewname($n)]
3787         }
3788         if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3789                 $newviewopts($n,cmd) ne $viewargscmd($n)} {
3790             set viewfiles($n) $files
3791             set viewargs($n) $newargs
3792             set viewargscmd($n) $newviewopts($n,cmd)
3793             if {$curview == $n} {
3794                 run reloadcommits
3795             }
3796         }
3797     }
3798     if {$apply} return
3799     catch {destroy $top}
3800 }
3801
3802 proc delview {} {
3803     global curview viewperm hlview selectedhlview
3804
3805     if {$curview == 0} return
3806     if {[info exists hlview] && $hlview == $curview} {
3807         set selectedhlview [mc "None"]
3808         unset hlview
3809     }
3810     allviewmenus $curview delete
3811     set viewperm($curview) 0
3812     showview 0
3813 }
3814
3815 proc addviewmenu {n} {
3816     global viewname viewhlmenu
3817
3818     .bar.view add radiobutton -label $viewname($n) \
3819         -command [list showview $n] -variable selectedview -value $n
3820     #$viewhlmenu add radiobutton -label $viewname($n) \
3821     #   -command [list addvhighlight $n] -variable selectedhlview
3822 }
3823
3824 proc showview {n} {
3825     global curview cached_commitrow ordertok
3826     global displayorder parentlist rowidlist rowisopt rowfinal
3827     global colormap rowtextx nextcolor canvxmax
3828     global numcommits viewcomplete
3829     global selectedline currentid canv canvy0
3830     global treediffs
3831     global pending_select mainheadid
3832     global commitidx
3833     global selectedview
3834     global hlview selectedhlview commitinterest
3835
3836     if {$n == $curview} return
3837     set selid {}
3838     set ymax [lindex [$canv cget -scrollregion] 3]
3839     set span [$canv yview]
3840     set ytop [expr {[lindex $span 0] * $ymax}]
3841     set ybot [expr {[lindex $span 1] * $ymax}]
3842     set yscreen [expr {($ybot - $ytop) / 2}]
3843     if {$selectedline ne {}} {
3844         set selid $currentid
3845         set y [yc $selectedline]
3846         if {$ytop < $y && $y < $ybot} {
3847             set yscreen [expr {$y - $ytop}]
3848         }
3849     } elseif {[info exists pending_select]} {
3850         set selid $pending_select
3851         unset pending_select
3852     }
3853     unselectline
3854     normalline
3855     catch {unset treediffs}
3856     clear_display
3857     if {[info exists hlview] && $hlview == $n} {
3858         unset hlview
3859         set selectedhlview [mc "None"]
3860     }
3861     catch {unset commitinterest}
3862     catch {unset cached_commitrow}
3863     catch {unset ordertok}
3864
3865     set curview $n
3866     set selectedview $n
3867     .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3868     .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3869
3870     run refill_reflist
3871     if {![info exists viewcomplete($n)]} {
3872         getcommits $selid
3873         return
3874     }
3875
3876     set displayorder {}
3877     set parentlist {}
3878     set rowidlist {}
3879     set rowisopt {}
3880     set rowfinal {}
3881     set numcommits $commitidx($n)
3882
3883     catch {unset colormap}
3884     catch {unset rowtextx}
3885     set nextcolor 0
3886     set canvxmax [$canv cget -width]
3887     set curview $n
3888     set row 0
3889     setcanvscroll
3890     set yf 0
3891     set row {}
3892     if {$selid ne {} && [commitinview $selid $n]} {
3893         set row [rowofcommit $selid]
3894         # try to get the selected row in the same position on the screen
3895         set ymax [lindex [$canv cget -scrollregion] 3]
3896         set ytop [expr {[yc $row] - $yscreen}]
3897         if {$ytop < 0} {
3898             set ytop 0
3899         }
3900         set yf [expr {$ytop * 1.0 / $ymax}]
3901     }
3902     allcanvs yview moveto $yf
3903     drawvisible
3904     if {$row ne {}} {
3905         selectline $row 0
3906     } elseif {!$viewcomplete($n)} {
3907         reset_pending_select $selid
3908     } else {
3909         reset_pending_select {}
3910
3911         if {[commitinview $pending_select $curview]} {
3912             selectline [rowofcommit $pending_select] 1
3913         } else {
3914             set row [first_real_row]
3915             if {$row < $numcommits} {
3916                 selectline $row 0
3917             }
3918         }
3919     }
3920     if {!$viewcomplete($n)} {
3921         if {$numcommits == 0} {
3922             show_status [mc "Reading commits..."]
3923         }
3924     } elseif {$numcommits == 0} {
3925         show_status [mc "No commits selected"]
3926     }
3927 }
3928
3929 # Stuff relating to the highlighting facility
3930
3931 proc ishighlighted {id} {
3932     global vhighlights fhighlights nhighlights rhighlights
3933
3934     if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3935         return $nhighlights($id)
3936     }
3937     if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3938         return $vhighlights($id)
3939     }
3940     if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3941         return $fhighlights($id)
3942     }
3943     if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3944         return $rhighlights($id)
3945     }
3946     return 0
3947 }
3948
3949 proc bolden {row font} {
3950     global canv linehtag selectedline boldrows
3951
3952     lappend boldrows $row
3953     $canv itemconf $linehtag($row) -font $font
3954     if {$row == $selectedline} {
3955         $canv delete secsel
3956         set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3957                    -outline {{}} -tags secsel \
3958                    -fill [$canv cget -selectbackground]]
3959         $canv lower $t
3960     }
3961 }
3962
3963 proc bolden_name {row font} {
3964     global canv2 linentag selectedline boldnamerows
3965
3966     lappend boldnamerows $row
3967     $canv2 itemconf $linentag($row) -font $font
3968     if {$row == $selectedline} {
3969         $canv2 delete secsel
3970         set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3971                    -outline {{}} -tags secsel \
3972                    -fill [$canv2 cget -selectbackground]]
3973         $canv2 lower $t
3974     }
3975 }
3976
3977 proc unbolden {} {
3978     global boldrows
3979
3980     set stillbold {}
3981     foreach row $boldrows {
3982         if {![ishighlighted [commitonrow $row]]} {
3983             bolden $row mainfont
3984         } else {
3985             lappend stillbold $row
3986         }
3987     }
3988     set boldrows $stillbold
3989 }
3990
3991 proc addvhighlight {n} {
3992     global hlview viewcomplete curview vhl_done commitidx
3993
3994     if {[info exists hlview]} {
3995         delvhighlight
3996     }
3997     set hlview $n
3998     if {$n != $curview && ![info exists viewcomplete($n)]} {
3999         start_rev_list $n
4000     }
4001     set vhl_done $commitidx($hlview)
4002     if {$vhl_done > 0} {
4003         drawvisible
4004     }
4005 }
4006
4007 proc delvhighlight {} {
4008     global hlview vhighlights
4009
4010     if {![info exists hlview]} return
4011     unset hlview
4012     catch {unset vhighlights}
4013     unbolden
4014 }
4015
4016 proc vhighlightmore {} {
4017     global hlview vhl_done commitidx vhighlights curview
4018
4019     set max $commitidx($hlview)
4020     set vr [visiblerows]
4021     set r0 [lindex $vr 0]
4022     set r1 [lindex $vr 1]
4023     for {set i $vhl_done} {$i < $max} {incr i} {
4024         set id [commitonrow $i $hlview]
4025         if {[commitinview $id $curview]} {
4026             set row [rowofcommit $id]
4027             if {$r0 <= $row && $row <= $r1} {
4028                 if {![highlighted $row]} {
4029                     bolden $row mainfontbold
4030                 }
4031                 set vhighlights($id) 1
4032             }
4033         }
4034     }
4035     set vhl_done $max
4036     return 0
4037 }
4038
4039 proc askvhighlight {row id} {
4040     global hlview vhighlights iddrawn
4041
4042     if {[commitinview $id $hlview]} {
4043         if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
4044             bolden $row mainfontbold
4045         }
4046         set vhighlights($id) 1
4047     } else {
4048         set vhighlights($id) 0
4049     }
4050 }
4051
4052 proc hfiles_change {} {
4053     global highlight_files filehighlight fhighlights fh_serial
4054     global highlight_paths gdttype
4055
4056     if {[info exists filehighlight]} {
4057         # delete previous highlights
4058         catch {close $filehighlight}
4059         unset filehighlight
4060         catch {unset fhighlights}
4061         unbolden
4062         unhighlight_filelist
4063     }
4064     set highlight_paths {}
4065     after cancel do_file_hl $fh_serial
4066     incr fh_serial
4067     if {$highlight_files ne {}} {
4068         after 300 do_file_hl $fh_serial
4069     }
4070 }
4071
4072 proc gdttype_change {name ix op} {
4073     global gdttype highlight_files findstring findpattern
4074
4075     stopfinding
4076     if {$findstring ne {}} {
4077         if {$gdttype eq [mc "containing:"]} {
4078             if {$highlight_files ne {}} {
4079                 set highlight_files {}
4080                 hfiles_change
4081             }
4082             findcom_change
4083         } else {
4084             if {$findpattern ne {}} {
4085                 set findpattern {}
4086                 findcom_change
4087             }
4088             set highlight_files $findstring
4089             hfiles_change
4090         }
4091         drawvisible
4092     }
4093     # enable/disable findtype/findloc menus too
4094 }
4095
4096 proc find_change {name ix op} {
4097     global gdttype findstring highlight_files
4098
4099     stopfinding
4100     if {$gdttype eq [mc "containing:"]} {
4101         findcom_change
4102     } else {
4103         if {$highlight_files ne $findstring} {
4104             set highlight_files $findstring
4105             hfiles_change
4106         }
4107     }
4108     drawvisible
4109 }
4110
4111 proc findcom_change args {
4112     global nhighlights boldnamerows
4113     global findpattern findtype findstring gdttype
4114
4115     stopfinding
4116     # delete previous highlights, if any
4117     foreach row $boldnamerows {
4118         bolden_name $row mainfont
4119     }
4120     set boldnamerows {}
4121     catch {unset nhighlights}
4122     unbolden
4123     unmarkmatches
4124     if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4125         set findpattern {}
4126     } elseif {$findtype eq [mc "Regexp"]} {
4127         set findpattern $findstring
4128     } else {
4129         set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4130                    $findstring]
4131         set findpattern "*$e*"
4132     }
4133 }
4134
4135 proc makepatterns {l} {
4136     set ret {}
4137     foreach e $l {
4138         set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4139         if {[string index $ee end] eq "/"} {
4140             lappend ret "$ee*"
4141         } else {
4142             lappend ret $ee
4143             lappend ret "$ee/*"
4144         }
4145     }
4146     return $ret
4147 }
4148
4149 proc do_file_hl {serial} {
4150     global highlight_files filehighlight highlight_paths gdttype fhl_list
4151
4152     if {$gdttype eq [mc "touching paths:"]} {
4153         if {[catch {set paths [shellsplit $highlight_files]}]} return
4154         set highlight_paths [makepatterns $paths]
4155         highlight_filelist
4156         set gdtargs [concat -- $paths]
4157     } elseif {$gdttype eq [mc "adding/removing string:"]} {
4158         set gdtargs [list "-S$highlight_files"]
4159     } else {
4160         # must be "containing:", i.e. we're searching commit info
4161         return
4162     }
4163     set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4164     set filehighlight [open $cmd r+]
4165     fconfigure $filehighlight -blocking 0
4166     filerun $filehighlight readfhighlight
4167     set fhl_list {}
4168     drawvisible
4169     flushhighlights
4170 }
4171
4172 proc flushhighlights {} {
4173     global filehighlight fhl_list
4174
4175     if {[info exists filehighlight]} {
4176         lappend fhl_list {}
4177         puts $filehighlight ""
4178         flush $filehighlight
4179     }
4180 }
4181
4182 proc askfilehighlight {row id} {
4183     global filehighlight fhighlights fhl_list
4184
4185     lappend fhl_list $id
4186     set fhighlights($id) -1
4187     puts $filehighlight $id
4188 }
4189
4190 proc readfhighlight {} {
4191     global filehighlight fhighlights curview iddrawn
4192     global fhl_list find_dirn
4193
4194     if {![info exists filehighlight]} {
4195         return 0
4196     }
4197     set nr 0
4198     while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4199         set line [string trim $line]
4200         set i [lsearch -exact $fhl_list $line]
4201         if {$i < 0} continue
4202         for {set j 0} {$j < $i} {incr j} {
4203             set id [lindex $fhl_list $j]
4204             set fhighlights($id) 0
4205         }
4206         set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4207         if {$line eq {}} continue
4208         if {![commitinview $line $curview]} continue
4209         set row [rowofcommit $line]
4210         if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4211             bolden $row mainfontbold
4212         }
4213         set fhighlights($line) 1
4214     }
4215     if {[eof $filehighlight]} {
4216         # strange...
4217         puts "oops, git diff-tree died"
4218         catch {close $filehighlight}
4219         unset filehighlight
4220         return 0
4221     }
4222     if {[info exists find_dirn]} {
4223         run findmore
4224     }
4225     return 1
4226 }
4227
4228 proc doesmatch {f} {
4229     global findtype findpattern
4230
4231     if {$findtype eq [mc "Regexp"]} {
4232         return [regexp $findpattern $f]
4233     } elseif {$findtype eq [mc "IgnCase"]} {
4234         return [string match -nocase $findpattern $f]
4235     } else {
4236         return [string match $findpattern $f]
4237     }
4238 }
4239
4240 proc askfindhighlight {row id} {
4241     global nhighlights commitinfo iddrawn
4242     global findloc
4243     global markingmatches
4244
4245     if {![info exists commitinfo($id)]} {
4246         getcommit $id
4247     }
4248     set info $commitinfo($id)
4249     set isbold 0
4250     set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4251     foreach f $info ty $fldtypes {
4252         if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4253             [doesmatch $f]} {
4254             if {$ty eq [mc "Author"]} {
4255                 set isbold 2
4256                 break
4257             }
4258             set isbold 1
4259         }
4260     }
4261     if {$isbold && [info exists iddrawn($id)]} {
4262         if {![ishighlighted $id]} {
4263             bolden $row mainfontbold
4264             if {$isbold > 1} {
4265                 bolden_name $row mainfontbold
4266             }
4267         }
4268         if {$markingmatches} {
4269             markrowmatches $row $id
4270         }
4271     }
4272     set nhighlights($id) $isbold
4273 }
4274
4275 proc markrowmatches {row id} {
4276     global canv canv2 linehtag linentag commitinfo findloc
4277
4278     set headline [lindex $commitinfo($id) 0]
4279     set author [lindex $commitinfo($id) 1]
4280     $canv delete match$row
4281     $canv2 delete match$row
4282     if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4283         set m [findmatches $headline]
4284         if {$m ne {}} {
4285             markmatches $canv $row $headline $linehtag($row) $m \
4286                 [$canv itemcget $linehtag($row) -font] $row
4287         }
4288     }
4289     if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4290         set m [findmatches $author]
4291         if {$m ne {}} {
4292             markmatches $canv2 $row $author $linentag($row) $m \
4293                 [$canv2 itemcget $linentag($row) -font] $row
4294         }
4295     }
4296 }
4297
4298 proc vrel_change {name ix op} {
4299     global highlight_related
4300
4301     rhighlight_none
4302     if {$highlight_related ne [mc "None"]} {
4303         run drawvisible
4304     }
4305 }
4306
4307 # prepare for testing whether commits are descendents or ancestors of a
4308 proc rhighlight_sel {a} {
4309     global descendent desc_todo ancestor anc_todo
4310     global highlight_related
4311
4312     catch {unset descendent}
4313     set desc_todo [list $a]
4314     catch {unset ancestor}
4315     set anc_todo [list $a]
4316     if {$highlight_related ne [mc "None"]} {
4317         rhighlight_none
4318         run drawvisible
4319     }
4320 }
4321
4322 proc rhighlight_none {} {
4323     global rhighlights
4324
4325     catch {unset rhighlights}
4326     unbolden
4327 }
4328
4329 proc is_descendent {a} {
4330     global curview children descendent desc_todo
4331
4332     set v $curview
4333     set la [rowofcommit $a]
4334     set todo $desc_todo
4335     set leftover {}
4336     set done 0
4337     for {set i 0} {$i < [llength $todo]} {incr i} {
4338         set do [lindex $todo $i]
4339         if {[rowofcommit $do] < $la} {
4340             lappend leftover $do
4341             continue
4342         }
4343         foreach nk $children($v,$do) {
4344             if {![info exists descendent($nk)]} {
4345                 set descendent($nk) 1
4346                 lappend todo $nk
4347                 if {$nk eq $a} {
4348                     set done 1
4349                 }
4350             }
4351         }
4352         if {$done} {
4353             set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4354             return
4355         }
4356     }
4357     set descendent($a) 0
4358     set desc_todo $leftover
4359 }
4360
4361 proc is_ancestor {a} {
4362     global curview parents ancestor anc_todo
4363
4364     set v $curview
4365     set la [rowofcommit $a]
4366     set todo $anc_todo
4367     set leftover {}
4368     set done 0
4369     for {set i 0} {$i < [llength $todo]} {incr i} {
4370         set do [lindex $todo $i]
4371         if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4372             lappend leftover $do
4373             continue
4374         }
4375         foreach np $parents($v,$do) {
4376             if {![info exists ancestor($np)]} {
4377                 set ancestor($np) 1
4378                 lappend todo $np
4379                 if {$np eq $a} {
4380                     set done 1
4381                 }
4382             }
4383         }
4384         if {$done} {
4385             set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4386             return
4387         }
4388     }
4389     set ancestor($a) 0
4390     set anc_todo $leftover
4391 }
4392
4393 proc askrelhighlight {row id} {
4394     global descendent highlight_related iddrawn rhighlights
4395     global selectedline ancestor
4396
4397     if {$selectedline eq {}} return
4398     set isbold 0
4399     if {$highlight_related eq [mc "Descendant"] ||
4400         $highlight_related eq [mc "Not descendant"]} {
4401         if {![info exists descendent($id)]} {
4402             is_descendent $id
4403         }
4404         if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4405             set isbold 1
4406         }
4407     } elseif {$highlight_related eq [mc "Ancestor"] ||
4408               $highlight_related eq [mc "Not ancestor"]} {
4409         if {![info exists ancestor($id)]} {
4410             is_ancestor $id
4411         }
4412         if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4413             set isbold 1
4414         }
4415     }
4416     if {[info exists iddrawn($id)]} {
4417         if {$isbold && ![ishighlighted $id]} {
4418             bolden $row mainfontbold
4419         }
4420     }
4421     set rhighlights($id) $isbold
4422 }
4423
4424 # Graph layout functions
4425
4426 proc shortids {ids} {
4427     set res {}
4428     foreach id $ids {
4429         if {[llength $id] > 1} {
4430             lappend res [shortids $id]
4431         } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4432             lappend res [string range $id 0 7]
4433         } else {
4434             lappend res $id
4435         }
4436     }
4437     return $res
4438 }
4439
4440 proc ntimes {n o} {
4441     set ret {}
4442     set o [list $o]
4443     for {set mask 1} {$mask <= $n} {incr mask $mask} {
4444         if {($n & $mask) != 0} {
4445             set ret [concat $ret $o]
4446         }
4447         set o [concat $o $o]
4448     }
4449     return $ret
4450 }
4451
4452 proc ordertoken {id} {
4453     global ordertok curview varcid varcstart varctok curview parents children
4454     global nullid nullid2
4455
4456     if {[info exists ordertok($id)]} {
4457         return $ordertok($id)
4458     }
4459     set origid $id
4460     set todo {}
4461     while {1} {
4462         if {[info exists varcid($curview,$id)]} {
4463             set a $varcid($curview,$id)
4464             set p [lindex $varcstart($curview) $a]
4465         } else {
4466             set p [lindex $children($curview,$id) 0]
4467         }
4468         if {[info exists ordertok($p)]} {
4469             set tok $ordertok($p)
4470             break
4471         }
4472         set id [first_real_child $curview,$p]
4473         if {$id eq {}} {
4474             # it's a root
4475             set tok [lindex $varctok($curview) $varcid($curview,$p)]
4476             break
4477         }
4478         if {[llength $parents($curview,$id)] == 1} {
4479             lappend todo [list $p {}]
4480         } else {
4481             set j [lsearch -exact $parents($curview,$id) $p]
4482             if {$j < 0} {
4483                 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4484             }
4485             lappend todo [list $p [strrep $j]]
4486         }
4487     }
4488     for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4489         set p [lindex $todo $i 0]
4490         append tok [lindex $todo $i 1]
4491         set ordertok($p) $tok
4492     }
4493     set ordertok($origid) $tok
4494     return $tok
4495 }
4496
4497 # Work out where id should go in idlist so that order-token
4498 # values increase from left to right
4499 proc idcol {idlist id {i 0}} {
4500     set t [ordertoken $id]
4501     if {$i < 0} {
4502         set i 0
4503     }
4504     if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4505         if {$i > [llength $idlist]} {
4506             set i [llength $idlist]
4507         }
4508         while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4509         incr i
4510     } else {
4511         if {$t > [ordertoken [lindex $idlist $i]]} {
4512             while {[incr i] < [llength $idlist] &&
4513                    $t >= [ordertoken [lindex $idlist $i]]} {}
4514         }
4515     }
4516     return $i
4517 }
4518
4519 proc initlayout {} {
4520     global rowidlist rowisopt rowfinal displayorder parentlist
4521     global numcommits canvxmax canv
4522     global nextcolor
4523     global colormap rowtextx
4524
4525     set numcommits 0
4526     set displayorder {}
4527     set parentlist {}
4528     set nextcolor 0
4529     set rowidlist {}
4530     set rowisopt {}
4531     set rowfinal {}
4532     set canvxmax [$canv cget -width]
4533     catch {unset colormap}
4534     catch {unset rowtextx}
4535     setcanvscroll
4536 }
4537
4538 proc setcanvscroll {} {
4539     global canv canv2 canv3 numcommits linespc canvxmax canvy0
4540     global lastscrollset lastscrollrows
4541
4542     set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4543     $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4544     $canv2 conf -scrollregion [list 0 0 0 $ymax]
4545     $canv3 conf -scrollregion [list 0 0 0 $ymax]
4546     set lastscrollset [clock clicks -milliseconds]
4547     set lastscrollrows $numcommits
4548 }
4549
4550 proc visiblerows {} {
4551     global canv numcommits linespc
4552
4553     set ymax [lindex [$canv cget -scrollregion] 3]
4554     if {$ymax eq {} || $ymax == 0} return
4555     set f [$canv yview]
4556     set y0 [expr {int([lindex $f 0] * $ymax)}]
4557     set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4558     if {$r0 < 0} {
4559         set r0 0
4560     }
4561     set y1 [expr {int([lindex $f 1] * $ymax)}]
4562     set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4563     if {$r1 >= $numcommits} {
4564         set r1 [expr {$numcommits - 1}]
4565     }
4566     return [list $r0 $r1]
4567 }
4568
4569 proc layoutmore {} {
4570     global commitidx viewcomplete curview
4571     global numcommits pending_select curview
4572     global lastscrollset lastscrollrows
4573
4574     if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4575         [clock clicks -milliseconds] - $lastscrollset > 500} {
4576         setcanvscroll
4577     }
4578     if {[info exists pending_select] &&
4579         [commitinview $pending_select $curview]} {
4580         update
4581         selectline [rowofcommit $pending_select] 1
4582     }
4583     drawvisible
4584 }
4585
4586 proc doshowlocalchanges {} {
4587     global curview mainheadid
4588
4589     if {$mainheadid eq {}} return
4590     if {[commitinview $mainheadid $curview]} {
4591         dodiffindex
4592     } else {
4593         interestedin $mainheadid dodiffindex
4594     }
4595 }
4596
4597 proc dohidelocalchanges {} {
4598     global nullid nullid2 lserial curview
4599
4600     if {[commitinview $nullid $curview]} {
4601         removefakerow $nullid
4602     }
4603     if {[commitinview $nullid2 $curview]} {
4604         removefakerow $nullid2
4605     }
4606     incr lserial
4607 }
4608
4609 # spawn off a process to do git diff-index --cached HEAD
4610 proc dodiffindex {} {
4611     global lserial showlocalchanges
4612     global isworktree
4613
4614     if {!$showlocalchanges || !$isworktree} return
4615     incr lserial
4616     set fd [open "|git diff-index --cached HEAD" r]
4617     fconfigure $fd -blocking 0
4618     set i [reg_instance $fd]
4619     filerun $fd [list readdiffindex $fd $lserial $i]
4620 }
4621
4622 proc readdiffindex {fd serial inst} {
4623     global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4624
4625     set isdiff 1
4626     if {[gets $fd line] < 0} {
4627         if {![eof $fd]} {
4628             return 1
4629         }
4630         set isdiff 0
4631     }
4632     # we only need to see one line and we don't really care what it says...
4633     stop_instance $inst
4634
4635     if {$serial != $lserial} {
4636         return 0
4637     }
4638
4639     # now see if there are any local changes not checked in to the index
4640     set fd [open "|git diff-files" r]
4641     fconfigure $fd -blocking 0
4642     set i [reg_instance $fd]
4643     filerun $fd [list readdifffiles $fd $serial $i]
4644
4645     if {$isdiff && ![commitinview $nullid2 $curview]} {
4646         # add the line for the changes in the index to the graph
4647         set hl [mc "Local changes checked in to index but not committed"]
4648         set commitinfo($nullid2) [list  $hl {} {} {} {} "    $hl\n"]
4649         set commitdata($nullid2) "\n    $hl\n"
4650         if {[commitinview $nullid $curview]} {
4651             removefakerow $nullid
4652         }
4653         insertfakerow $nullid2 $mainheadid
4654     } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4655         removefakerow $nullid2
4656     }
4657     return 0
4658 }
4659
4660 proc readdifffiles {fd serial inst} {
4661     global mainheadid nullid nullid2 curview
4662     global commitinfo commitdata lserial
4663
4664     set isdiff 1
4665     if {[gets $fd line] < 0} {
4666         if {![eof $fd]} {
4667             return 1
4668         }
4669         set isdiff 0
4670     }
4671     # we only need to see one line and we don't really care what it says...
4672     stop_instance $inst
4673
4674     if {$serial != $lserial} {
4675         return 0
4676     }
4677
4678     if {$isdiff && ![commitinview $nullid $curview]} {
4679         # add the line for the local diff to the graph
4680         set hl [mc "Local uncommitted changes, not checked in to index"]
4681         set commitinfo($nullid) [list  $hl {} {} {} {} "    $hl\n"]
4682         set commitdata($nullid) "\n    $hl\n"
4683         if {[commitinview $nullid2 $curview]} {
4684             set p $nullid2
4685         } else {
4686             set p $mainheadid
4687         }
4688         insertfakerow $nullid $p
4689     } elseif {!$isdiff && [commitinview $nullid $curview]} {
4690         removefakerow $nullid
4691     }
4692     return 0
4693 }
4694
4695 proc nextuse {id row} {
4696     global curview children
4697
4698     if {[info exists children($curview,$id)]} {
4699         foreach kid $children($curview,$id) {
4700             if {![commitinview $kid $curview]} {
4701                 return -1
4702             }
4703             if {[rowofcommit $kid] > $row} {
4704                 return [rowofcommit $kid]
4705             }
4706         }
4707     }
4708     if {[commitinview $id $curview]} {
4709         return [rowofcommit $id]
4710     }
4711     return -1
4712 }
4713
4714 proc prevuse {id row} {
4715     global curview children
4716
4717     set ret -1
4718     if {[info exists children($curview,$id)]} {
4719         foreach kid $children($curview,$id) {
4720             if {![commitinview $kid $curview]} break
4721             if {[rowofcommit $kid] < $row} {
4722                 set ret [rowofcommit $kid]
4723             }
4724         }
4725     }
4726     return $ret
4727 }
4728
4729 proc make_idlist {row} {
4730     global displayorder parentlist uparrowlen downarrowlen mingaplen
4731     global commitidx curview children
4732
4733     set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4734     if {$r < 0} {
4735         set r 0
4736     }
4737     set ra [expr {$row - $downarrowlen}]
4738     if {$ra < 0} {
4739         set ra 0
4740     }
4741     set rb [expr {$row + $uparrowlen}]
4742     if {$rb > $commitidx($curview)} {
4743         set rb $commitidx($curview)
4744     }
4745     make_disporder $r [expr {$rb + 1}]
4746     set ids {}
4747     for {} {$r < $ra} {incr r} {
4748         set nextid [lindex $displayorder [expr {$r + 1}]]
4749         foreach p [lindex $parentlist $r] {
4750             if {$p eq $nextid} continue
4751             set rn [nextuse $p $r]
4752             if {$rn >= $row &&
4753                 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4754                 lappend ids [list [ordertoken $p] $p]
4755             }
4756         }
4757     }
4758     for {} {$r < $row} {incr r} {
4759         set nextid [lindex $displayorder [expr {$r + 1}]]
4760         foreach p [lindex $parentlist $r] {
4761             if {$p eq $nextid} continue
4762             set rn [nextuse $p $r]
4763             if {$rn < 0 || $rn >= $row} {
4764                 lappend ids [list [ordertoken $p] $p]
4765             }
4766         }
4767     }
4768     set id [lindex $displayorder $row]
4769     lappend ids [list [ordertoken $id] $id]
4770     while {$r < $rb} {
4771         foreach p [lindex $parentlist $r] {
4772             set firstkid [lindex $children($curview,$p) 0]
4773             if {[rowofcommit $firstkid] < $row} {
4774                 lappend ids [list [ordertoken $p] $p]
4775             }
4776         }
4777         incr r
4778         set id [lindex $displayorder $r]
4779         if {$id ne {}} {
4780             set firstkid [lindex $children($curview,$id) 0]
4781             if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4782                 lappend ids [list [ordertoken $id] $id]
4783             }
4784         }
4785     }
4786     set idlist {}
4787     foreach idx [lsort -unique $ids] {
4788         lappend idlist [lindex $idx 1]
4789     }
4790     return $idlist
4791 }
4792
4793 proc rowsequal {a b} {
4794     while {[set i [lsearch -exact $a {}]] >= 0} {
4795         set a [lreplace $a $i $i]
4796     }
4797     while {[set i [lsearch -exact $b {}]] >= 0} {
4798         set b [lreplace $b $i $i]
4799     }
4800     return [expr {$a eq $b}]
4801 }
4802
4803 proc makeupline {id row rend col} {
4804     global rowidlist uparrowlen downarrowlen mingaplen
4805
4806     for {set r $rend} {1} {set r $rstart} {
4807         set rstart [prevuse $id $r]
4808         if {$rstart < 0} return
4809         if {$rstart < $row} break
4810     }
4811     if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4812         set rstart [expr {$rend - $uparrowlen - 1}]
4813     }
4814     for {set r $rstart} {[incr r] <= $row} {} {
4815         set idlist [lindex $rowidlist $r]
4816         if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4817             set col [idcol $idlist $id $col]
4818             lset rowidlist $r [linsert $idlist $col $id]
4819             changedrow $r
4820         }
4821     }
4822 }
4823
4824 proc layoutrows {row endrow} {
4825     global rowidlist rowisopt rowfinal displayorder
4826     global uparrowlen downarrowlen maxwidth mingaplen
4827     global children parentlist
4828     global commitidx viewcomplete curview
4829
4830     make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4831     set idlist {}
4832     if {$row > 0} {
4833         set rm1 [expr {$row - 1}]
4834         foreach id [lindex $rowidlist $rm1] {
4835             if {$id ne {}} {
4836                 lappend idlist $id
4837             }
4838         }
4839         set final [lindex $rowfinal $rm1]
4840     }
4841     for {} {$row < $endrow} {incr row} {
4842         set rm1 [expr {$row - 1}]
4843         if {$rm1 < 0 || $idlist eq {}} {
4844             set idlist [make_idlist $row]
4845             set final 1
4846         } else {
4847             set id [lindex $displayorder $rm1]
4848             set col [lsearch -exact $idlist $id]
4849             set idlist [lreplace $idlist $col $col]
4850             foreach p [lindex $parentlist $rm1] {
4851                 if {[lsearch -exact $idlist $p] < 0} {
4852                     set col [idcol $idlist $p $col]
4853                     set idlist [linsert $idlist $col $p]
4854                     # if not the first child, we have to insert a line going up
4855                     if {$id ne [lindex $children($curview,$p) 0]} {
4856                         makeupline $p $rm1 $row $col
4857                     }
4858                 }
4859             }
4860             set id [lindex $displayorder $row]
4861             if {$row > $downarrowlen} {
4862                 set termrow [expr {$row - $downarrowlen - 1}]
4863                 foreach p [lindex $parentlist $termrow] {
4864                     set i [lsearch -exact $idlist $p]
4865                     if {$i < 0} continue
4866                     set nr [nextuse $p $termrow]
4867                     if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4868                         set idlist [lreplace $idlist $i $i]
4869                     }
4870                 }
4871             }
4872             set col [lsearch -exact $idlist $id]
4873             if {$col < 0} {
4874                 set col [idcol $idlist $id]
4875                 set idlist [linsert $idlist $col $id]
4876                 if {$children($curview,$id) ne {}} {
4877                     makeupline $id $rm1 $row $col
4878                 }
4879             }
4880             set r [expr {$row + $uparrowlen - 1}]
4881             if {$r < $commitidx($curview)} {
4882                 set x $col
4883                 foreach p [lindex $parentlist $r] {
4884                     if {[lsearch -exact $idlist $p] >= 0} continue
4885                     set fk [lindex $children($curview,$p) 0]
4886                     if {[rowofcommit $fk] < $row} {
4887                         set x [idcol $idlist $p $x]
4888                         set idlist [linsert $idlist $x $p]
4889                     }
4890                 }
4891                 if {[incr r] < $commitidx($curview)} {
4892                     set p [lindex $displayorder $r]
4893                     if {[lsearch -exact $idlist $p] < 0} {
4894                         set fk [lindex $children($curview,$p) 0]
4895                         if {$fk ne {} && [rowofcommit $fk] < $row} {
4896                             set x [idcol $idlist $p $x]
4897                             set idlist [linsert $idlist $x $p]
4898                         }
4899                     }
4900                 }
4901             }
4902         }
4903         if {$final && !$viewcomplete($curview) &&
4904             $row + $uparrowlen + $mingaplen + $downarrowlen
4905                 >= $commitidx($curview)} {
4906             set final 0
4907         }
4908         set l [llength $rowidlist]
4909         if {$row == $l} {
4910             lappend rowidlist $idlist
4911             lappend rowisopt 0
4912             lappend rowfinal $final
4913         } elseif {$row < $l} {
4914             if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4915                 lset rowidlist $row $idlist
4916                 changedrow $row
4917             }
4918             lset rowfinal $row $final
4919         } else {
4920             set pad [ntimes [expr {$row - $l}] {}]
4921             set rowidlist [concat $rowidlist $pad]
4922             lappend rowidlist $idlist
4923             set rowfinal [concat $rowfinal $pad]
4924             lappend rowfinal $final
4925             set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4926         }
4927     }
4928     return $row
4929 }
4930
4931 proc changedrow {row} {
4932     global displayorder iddrawn rowisopt need_redisplay
4933
4934     set l [llength $rowisopt]
4935     if {$row < $l} {
4936         lset rowisopt $row 0
4937         if {$row + 1 < $l} {
4938             lset rowisopt [expr {$row + 1}] 0
4939             if {$row + 2 < $l} {
4940                 lset rowisopt [expr {$row + 2}] 0
4941             }
4942         }
4943     }
4944     set id [lindex $displayorder $row]
4945     if {[info exists iddrawn($id)]} {
4946         set need_redisplay 1
4947     }
4948 }
4949
4950 proc insert_pad {row col npad} {
4951     global rowidlist
4952
4953     set pad [ntimes $npad {}]
4954     set idlist [lindex $rowidlist $row]
4955     set bef [lrange $idlist 0 [expr {$col - 1}]]
4956     set aft [lrange $idlist $col end]
4957     set i [lsearch -exact $aft {}]
4958     if {$i > 0} {
4959         set aft [lreplace $aft $i $i]
4960     }
4961     lset rowidlist $row [concat $bef $pad $aft]
4962     changedrow $row
4963 }
4964
4965 proc optimize_rows {row col endrow} {
4966     global rowidlist rowisopt displayorder curview children
4967
4968     if {$row < 1} {
4969         set row 1
4970     }
4971     for {} {$row < $endrow} {incr row; set col 0} {
4972         if {[lindex $rowisopt $row]} continue
4973         set haspad 0
4974         set y0 [expr {$row - 1}]
4975         set ym [expr {$row - 2}]
4976         set idlist [lindex $rowidlist $row]
4977         set previdlist [lindex $rowidlist $y0]
4978         if {$idlist eq {} || $previdlist eq {}} continue
4979         if {$ym >= 0} {
4980             set pprevidlist [lindex $rowidlist $ym]
4981             if {$pprevidlist eq {}} continue
4982         } else {
4983             set pprevidlist {}
4984         }
4985         set x0 -1
4986         set xm -1
4987         for {} {$col < [llength $idlist]} {incr col} {
4988             set id [lindex $idlist $col]
4989             if {[lindex $previdlist $col] eq $id} continue
4990             if {$id eq {}} {
4991                 set haspad 1
4992                 continue
4993             }
4994             set x0 [lsearch -exact $previdlist $id]
4995             if {$x0 < 0} continue
4996             set z [expr {$x0 - $col}]
4997             set isarrow 0
4998             set z0 {}
4999             if {$ym >= 0} {
5000                 set xm [lsearch -exact $pprevidlist $id]
5001                 if {$xm >= 0} {
5002                     set z0 [expr {$xm - $x0}]
5003                 }
5004             }
5005             if {$z0 eq {}} {
5006                 # if row y0 is the first child of $id then it's not an arrow
5007                 if {[lindex $children($curview,$id) 0] ne
5008                     [lindex $displayorder $y0]} {
5009                     set isarrow 1
5010                 }
5011             }
5012             if {!$isarrow && $id ne [lindex $displayorder $row] &&
5013                 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
5014                 set isarrow 1
5015             }
5016             # Looking at lines from this row to the previous row,
5017             # make them go straight up if they end in an arrow on
5018             # the previous row; otherwise make them go straight up
5019             # or at 45 degrees.
5020             if {$z < -1 || ($z < 0 && $isarrow)} {
5021                 # Line currently goes left too much;
5022                 # insert pads in the previous row, then optimize it
5023                 set npad [expr {-1 - $z + $isarrow}]
5024                 insert_pad $y0 $x0 $npad
5025                 if {$y0 > 0} {
5026                     optimize_rows $y0 $x0 $row
5027                 }
5028                 set previdlist [lindex $rowidlist $y0]
5029                 set x0 [lsearch -exact $previdlist $id]
5030                 set z [expr {$x0 - $col}]
5031                 if {$z0 ne {}} {
5032                     set pprevidlist [lindex $rowidlist $ym]
5033                     set xm [lsearch -exact $pprevidlist $id]
5034                     set z0 [expr {$xm - $x0}]
5035                 }
5036             } elseif {$z > 1 || ($z > 0 && $isarrow)} {
5037                 # Line currently goes right too much;
5038                 # insert pads in this line
5039                 set npad [expr {$z - 1 + $isarrow}]
5040                 insert_pad $row $col $npad
5041                 set idlist [lindex $rowidlist $row]
5042                 incr col $npad
5043                 set z [expr {$x0 - $col}]
5044                 set haspad 1
5045             }
5046             if {$z0 eq {} && !$isarrow && $ym >= 0} {
5047                 # this line links to its first child on row $row-2
5048                 set id [lindex $displayorder $ym]
5049                 set xc [lsearch -exact $pprevidlist $id]
5050                 if {$xc >= 0} {
5051                     set z0 [expr {$xc - $x0}]
5052                 }
5053             }
5054             # avoid lines jigging left then immediately right
5055             if {$z0 ne {} && $z < 0 && $z0 > 0} {
5056                 insert_pad $y0 $x0 1
5057                 incr x0
5058                 optimize_rows $y0 $x0 $row
5059                 set previdlist [lindex $rowidlist $y0]
5060             }
5061         }
5062         if {!$haspad} {
5063             # Find the first column that doesn't have a line going right
5064             for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5065                 set id [lindex $idlist $col]
5066                 if {$id eq {}} break
5067                 set x0 [lsearch -exact $previdlist $id]
5068                 if {$x0 < 0} {
5069                     # check if this is the link to the first child
5070                     set kid [lindex $displayorder $y0]
5071                     if {[lindex $children($curview,$id) 0] eq $kid} {
5072                         # it is, work out offset to child
5073                         set x0 [lsearch -exact $previdlist $kid]
5074                     }
5075                 }
5076                 if {$x0 <= $col} break
5077             }
5078             # Insert a pad at that column as long as it has a line and
5079             # isn't the last column
5080             if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5081                 set idlist [linsert $idlist $col {}]
5082                 lset rowidlist $row $idlist
5083                 changedrow $row
5084             }
5085         }
5086     }
5087 }
5088
5089 proc xc {row col} {
5090     global canvx0 linespc
5091     return [expr {$canvx0 + $col * $linespc}]
5092 }
5093
5094 proc yc {row} {
5095     global canvy0 linespc
5096     return [expr {$canvy0 + $row * $linespc}]
5097 }
5098
5099 proc linewidth {id} {
5100     global thickerline lthickness
5101
5102     set wid $lthickness
5103     if {[info exists thickerline] && $id eq $thickerline} {
5104         set wid [expr {2 * $lthickness}]
5105     }
5106     return $wid
5107 }
5108
5109 proc rowranges {id} {
5110     global curview children uparrowlen downarrowlen
5111     global rowidlist
5112
5113     set kids $children($curview,$id)
5114     if {$kids eq {}} {
5115         return {}
5116     }
5117     set ret {}
5118     lappend kids $id
5119     foreach child $kids {
5120         if {![commitinview $child $curview]} break
5121         set row [rowofcommit $child]
5122         if {![info exists prev]} {
5123             lappend ret [expr {$row + 1}]
5124         } else {
5125             if {$row <= $prevrow} {
5126                 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5127             }
5128             # see if the line extends the whole way from prevrow to row
5129             if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5130                 [lsearch -exact [lindex $rowidlist \
5131                             [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5132                 # it doesn't, see where it ends
5133                 set r [expr {$prevrow + $downarrowlen}]
5134                 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5135                     while {[incr r -1] > $prevrow &&
5136                            [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5137                 } else {
5138                     while {[incr r] <= $row &&
5139                            [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5140                     incr r -1
5141                 }
5142                 lappend ret $r
5143                 # see where it starts up again
5144                 set r [expr {$row - $uparrowlen}]
5145                 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5146                     while {[incr r] < $row &&
5147                            [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5148                 } else {
5149                     while {[incr r -1] >= $prevrow &&
5150                            [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5151                     incr r
5152                 }
5153                 lappend ret $r
5154             }
5155         }
5156         if {$child eq $id} {
5157             lappend ret $row
5158         }
5159         set prev $child
5160         set prevrow $row
5161     }
5162     return $ret
5163 }
5164
5165 proc drawlineseg {id row endrow arrowlow} {
5166     global rowidlist displayorder iddrawn linesegs
5167     global canv colormap linespc curview maxlinelen parentlist
5168
5169     set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5170     set le [expr {$row + 1}]
5171     set arrowhigh 1
5172     while {1} {
5173         set c [lsearch -exact [lindex $rowidlist $le] $id]
5174         if {$c < 0} {
5175             incr le -1
5176             break
5177         }
5178         lappend cols $c
5179         set x [lindex $displayorder $le]
5180         if {$x eq $id} {
5181             set arrowhigh 0
5182             break
5183         }
5184         if {[info exists iddrawn($x)] || $le == $endrow} {
5185             set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5186             if {$c >= 0} {
5187                 lappend cols $c
5188                 set arrowhigh 0
5189             }
5190             break
5191         }
5192         incr le
5193     }
5194     if {$le <= $row} {
5195         return $row
5196     }
5197
5198     set lines {}
5199     set i 0
5200     set joinhigh 0
5201     if {[info exists linesegs($id)]} {
5202         set lines $linesegs($id)
5203         foreach li $lines {
5204             set r0 [lindex $li 0]
5205             if {$r0 > $row} {
5206                 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5207                     set joinhigh 1
5208                 }
5209                 break
5210             }
5211             incr i
5212         }
5213     }
5214     set joinlow 0
5215     if {$i > 0} {
5216         set li [lindex $lines [expr {$i-1}]]
5217         set r1 [lindex $li 1]
5218         if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5219             set joinlow 1
5220         }
5221     }
5222
5223     set x [lindex $cols [expr {$le - $row}]]
5224     set xp [lindex $cols [expr {$le - 1 - $row}]]
5225     set dir [expr {$xp - $x}]
5226     if {$joinhigh} {
5227         set ith [lindex $lines $i 2]
5228         set coords [$canv coords $ith]
5229         set ah [$canv itemcget $ith -arrow]
5230         set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5231         set x2 [lindex $cols [expr {$le + 1 - $row}]]
5232         if {$x2 ne {} && $x - $x2 == $dir} {
5233             set coords [lrange $coords 0 end-2]
5234         }
5235     } else {
5236         set coords [list [xc $le $x] [yc $le]]
5237     }
5238     if {$joinlow} {
5239         set itl [lindex $lines [expr {$i-1}] 2]
5240         set al [$canv itemcget $itl -arrow]
5241         set arrowlow [expr {$al eq "last" || $al eq "both"}]
5242     } elseif {$arrowlow} {
5243         if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5244             [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5245             set arrowlow 0
5246         }
5247     }
5248     set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5249     for {set y $le} {[incr y -1] > $row} {} {
5250         set x $xp
5251         set xp [lindex $cols [expr {$y - 1 - $row}]]
5252         set ndir [expr {$xp - $x}]
5253         if {$dir != $ndir || $xp < 0} {
5254             lappend coords [xc $y $x] [yc $y]
5255         }
5256         set dir $ndir
5257     }
5258     if {!$joinlow} {
5259         if {$xp < 0} {
5260             # join parent line to first child
5261             set ch [lindex $displayorder $row]
5262             set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5263             if {$xc < 0} {
5264                 puts "oops: drawlineseg: child $ch not on row $row"
5265             } elseif {$xc != $x} {
5266                 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5267                     set d [expr {int(0.5 * $linespc)}]
5268                     set x1 [xc $row $x]
5269                     if {$xc < $x} {
5270                         set x2 [expr {$x1 - $d}]
5271                     } else {
5272                         set x2 [expr {$x1 + $d}]
5273                     }
5274                     set y2 [yc $row]
5275                     set y1 [expr {$y2 + $d}]
5276                     lappend coords $x1 $y1 $x2 $y2
5277                 } elseif {$xc < $x - 1} {
5278                     lappend coords [xc $row [expr {$x-1}]] [yc $row]
5279                 } elseif {$xc > $x + 1} {
5280                     lappend coords [xc $row [expr {$x+1}]] [yc $row]
5281                 }
5282                 set x $xc
5283             }
5284             lappend coords [xc $row $x] [yc $row]
5285         } else {
5286             set xn [xc $row $xp]
5287             set yn [yc $row]
5288             lappend coords $xn $yn
5289         }
5290         if {!$joinhigh} {
5291             assigncolor $id
5292             set t [$canv create line $coords -width [linewidth $id] \
5293                        -fill $colormap($id) -tags lines.$id -arrow $arrow]
5294             $canv lower $t
5295             bindline $t $id
5296             set lines [linsert $lines $i [list $row $le $t]]
5297         } else {
5298             $canv coords $ith $coords
5299             if {$arrow ne $ah} {
5300                 $canv itemconf $ith -arrow $arrow
5301             }
5302             lset lines $i 0 $row
5303         }
5304     } else {
5305         set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5306         set ndir [expr {$xo - $xp}]
5307         set clow [$canv coords $itl]
5308         if {$dir == $ndir} {
5309             set clow [lrange $clow 2 end]
5310         }
5311         set coords [concat $coords $clow]
5312         if {!$joinhigh} {
5313             lset lines [expr {$i-1}] 1 $le
5314         } else {
5315             # coalesce two pieces
5316             $canv delete $ith
5317             set b [lindex $lines [expr {$i-1}] 0]
5318             set e [lindex $lines $i 1]
5319             set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5320         }
5321         $canv coords $itl $coords
5322         if {$arrow ne $al} {
5323             $canv itemconf $itl -arrow $arrow
5324         }
5325     }
5326
5327     set linesegs($id) $lines
5328     return $le
5329 }
5330
5331 proc drawparentlinks {id row} {
5332     global rowidlist canv colormap curview parentlist
5333     global idpos linespc
5334
5335     set rowids [lindex $rowidlist $row]
5336     set col [lsearch -exact $rowids $id]
5337     if {$col < 0} return
5338     set olds [lindex $parentlist $row]
5339     set row2 [expr {$row + 1}]
5340     set x [xc $row $col]
5341     set y [yc $row]
5342     set y2 [yc $row2]
5343     set d [expr {int(0.5 * $linespc)}]
5344     set ymid [expr {$y + $d}]
5345     set ids [lindex $rowidlist $row2]
5346     # rmx = right-most X coord used
5347     set rmx 0
5348     foreach p $olds {
5349         set i [lsearch -exact $ids $p]
5350         if {$i < 0} {
5351             puts "oops, parent $p of $id not in list"
5352             continue
5353         }
5354         set x2 [xc $row2 $i]
5355         if {$x2 > $rmx} {
5356             set rmx $x2
5357         }
5358         set j [lsearch -exact $rowids $p]
5359         if {$j < 0} {
5360             # drawlineseg will do this one for us
5361             continue
5362         }
5363         assigncolor $p
5364         # should handle duplicated parents here...
5365         set coords [list $x $y]
5366         if {$i != $col} {
5367             # if attaching to a vertical segment, draw a smaller
5368             # slant for visual distinctness
5369             if {$i == $j} {
5370                 if {$i < $col} {
5371                     lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5372                 } else {
5373                     lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5374                 }
5375             } elseif {$i < $col && $i < $j} {
5376                 # segment slants towards us already
5377                 lappend coords [xc $row $j] $y
5378             } else {
5379                 if {$i < $col - 1} {
5380                     lappend coords [expr {$x2 + $linespc}] $y
5381                 } elseif {$i > $col + 1} {
5382                     lappend coords [expr {$x2 - $linespc}] $y
5383                 }
5384                 lappend coords $x2 $y2
5385             }
5386         } else {
5387             lappend coords $x2 $y2
5388         }
5389         set t [$canv create line $coords -width [linewidth $p] \
5390                    -fill $colormap($p) -tags lines.$p]
5391         $canv lower $t
5392         bindline $t $p
5393     }
5394     if {$rmx > [lindex $idpos($id) 1]} {
5395         lset idpos($id) 1 $rmx
5396         redrawtags $id
5397     }
5398 }
5399
5400 proc drawlines {id} {
5401     global canv
5402
5403     $canv itemconf lines.$id -width [linewidth $id]
5404 }
5405
5406 proc drawcmittext {id row col} {
5407     global linespc canv canv2 canv3 fgcolor curview
5408     global cmitlisted commitinfo rowidlist parentlist
5409     global rowtextx idpos idtags idheads idotherrefs
5410     global linehtag linentag linedtag selectedline
5411     global canvxmax boldrows boldnamerows fgcolor
5412     global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5413
5414     # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5415     set listed $cmitlisted($curview,$id)
5416     if {$id eq $nullid} {
5417         set ofill red
5418     } elseif {$id eq $nullid2} {
5419         set ofill green
5420     } elseif {$id eq $mainheadid} {
5421         set ofill yellow
5422     } else {
5423         set ofill [lindex $circlecolors $listed]
5424     }
5425     set x [xc $row $col]
5426     set y [yc $row]
5427     set orad [expr {$linespc / 3}]
5428     if {$listed <= 2} {
5429         set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5430                    [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5431                    -fill $ofill -outline $fgcolor -width 1 -tags circle]
5432     } elseif {$listed == 3} {
5433         # triangle pointing left for left-side commits
5434         set t [$canv create polygon \
5435                    [expr {$x - $orad}] $y \
5436                    [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5437                    [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5438                    -fill $ofill -outline $fgcolor -width 1 -tags circle]
5439     } else {
5440         # triangle pointing right for right-side commits
5441         set t [$canv create polygon \
5442                    [expr {$x + $orad - 1}] $y \
5443                    [expr {$x - $orad}] [expr {$y - $orad}] \
5444                    [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5445                    -fill $ofill -outline $fgcolor -width 1 -tags circle]
5446     }
5447     set circleitem($row) $t
5448     $canv raise $t
5449     $canv bind $t <1> {selcanvline {} %x %y}
5450     set rmx [llength [lindex $rowidlist $row]]
5451     set olds [lindex $parentlist $row]
5452     if {$olds ne {}} {
5453         set nextids [lindex $rowidlist [expr {$row + 1}]]
5454         foreach p $olds {
5455             set i [lsearch -exact $nextids $p]
5456             if {$i > $rmx} {
5457                 set rmx $i
5458             }
5459         }
5460     }
5461     set xt [xc $row $rmx]
5462     set rowtextx($row) $xt
5463     set idpos($id) [list $x $xt $y]
5464     if {[info exists idtags($id)] || [info exists idheads($id)]
5465         || [info exists idotherrefs($id)]} {
5466         set xt [drawtags $id $x $xt $y]
5467     }
5468     set headline [lindex $commitinfo($id) 0]
5469     set name [lindex $commitinfo($id) 1]
5470     set date [lindex $commitinfo($id) 2]
5471     set date [formatdate $date]
5472     set font mainfont
5473     set nfont mainfont
5474     set isbold [ishighlighted $id]
5475     if {$isbold > 0} {
5476         lappend boldrows $row
5477         set font mainfontbold
5478         if {$isbold > 1} {
5479             lappend boldnamerows $row
5480             set nfont mainfontbold
5481         }
5482     }
5483     set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
5484                             -text $headline -font $font -tags text]
5485     $canv bind $linehtag($row) $ctxbut "rowmenu %X %Y $id"
5486     set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5487                             -text $name -font $nfont -tags text]
5488     set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5489                             -text $date -font mainfont -tags text]
5490     if {$selectedline == $row} {
5491         make_secsel $row
5492     }
5493     set xr [expr {$xt + [font measure $font $headline]}]
5494     if {$xr > $canvxmax} {
5495         set canvxmax $xr
5496         setcanvscroll
5497     }
5498 }
5499
5500 proc drawcmitrow {row} {
5501     global displayorder rowidlist nrows_drawn
5502     global iddrawn markingmatches
5503     global commitinfo numcommits
5504     global filehighlight fhighlights findpattern nhighlights
5505     global hlview vhighlights
5506     global highlight_related rhighlights
5507
5508     if {$row >= $numcommits} return
5509
5510     set id [lindex $displayorder $row]
5511     if {[info exists hlview] && ![info exists vhighlights($id)]} {
5512         askvhighlight $row $id
5513     }
5514     if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5515         askfilehighlight $row $id
5516     }
5517     if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5518         askfindhighlight $row $id
5519     }
5520     if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5521         askrelhighlight $row $id
5522     }
5523     if {![info exists iddrawn($id)]} {
5524         set col [lsearch -exact [lindex $rowidlist $row] $id]
5525         if {$col < 0} {
5526             puts "oops, row $row id $id not in list"
5527             return
5528         }
5529         if {![info exists commitinfo($id)]} {
5530             getcommit $id
5531         }
5532         assigncolor $id
5533         drawcmittext $id $row $col
5534         set iddrawn($id) 1
5535         incr nrows_drawn
5536     }
5537     if {$markingmatches} {
5538         markrowmatches $row $id
5539     }
5540 }
5541
5542 proc drawcommits {row {endrow {}}} {
5543     global numcommits iddrawn displayorder curview need_redisplay
5544     global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5545
5546     if {$row < 0} {
5547         set row 0
5548     }
5549     if {$endrow eq {}} {
5550         set endrow $row
5551     }
5552     if {$endrow >= $numcommits} {
5553         set endrow [expr {$numcommits - 1}]
5554     }
5555
5556     set rl1 [expr {$row - $downarrowlen - 3}]
5557     if {$rl1 < 0} {
5558         set rl1 0
5559     }
5560     set ro1 [expr {$row - 3}]
5561     if {$ro1 < 0} {
5562         set ro1 0
5563     }
5564     set r2 [expr {$endrow + $uparrowlen + 3}]
5565     if {$r2 > $numcommits} {
5566         set r2 $numcommits
5567     }
5568     for {set r $rl1} {$r < $r2} {incr r} {
5569         if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5570             if {$rl1 < $r} {
5571                 layoutrows $rl1 $r
5572             }
5573             set rl1 [expr {$r + 1}]
5574         }
5575     }
5576     if {$rl1 < $r} {
5577         layoutrows $rl1 $r
5578     }
5579     optimize_rows $ro1 0 $r2
5580     if {$need_redisplay || $nrows_drawn > 2000} {
5581         clear_display
5582         drawvisible
5583     }
5584
5585     # make the lines join to already-drawn rows either side
5586     set r [expr {$row - 1}]
5587     if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5588         set r $row
5589     }
5590     set er [expr {$endrow + 1}]
5591     if {$er >= $numcommits ||
5592         ![info exists iddrawn([lindex $displayorder $er])]} {
5593         set er $endrow
5594     }
5595     for {} {$r <= $er} {incr r} {
5596         set id [lindex $displayorder $r]
5597         set wasdrawn [info exists iddrawn($id)]
5598         drawcmitrow $r
5599         if {$r == $er} break
5600         set nextid [lindex $displayorder [expr {$r + 1}]]
5601         if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5602         drawparentlinks $id $r
5603
5604         set rowids [lindex $rowidlist $r]
5605         foreach lid $rowids {
5606             if {$lid eq {}} continue
5607             if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5608             if {$lid eq $id} {
5609                 # see if this is the first child of any of its parents
5610                 foreach p [lindex $parentlist $r] {
5611                     if {[lsearch -exact $rowids $p] < 0} {
5612                         # make this line extend up to the child
5613                         set lineend($p) [drawlineseg $p $r $er 0]
5614                     }
5615                 }
5616             } else {
5617                 set lineend($lid) [drawlineseg $lid $r $er 1]
5618             }
5619         }
5620     }
5621 }
5622
5623 proc undolayout {row} {
5624     global uparrowlen mingaplen downarrowlen
5625     global rowidlist rowisopt rowfinal need_redisplay
5626
5627     set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5628     if {$r < 0} {
5629         set r 0
5630     }
5631     if {[llength $rowidlist] > $r} {
5632         incr r -1
5633         set rowidlist [lrange $rowidlist 0 $r]
5634         set rowfinal [lrange $rowfinal 0 $r]
5635         set rowisopt [lrange $rowisopt 0 $r]
5636         set need_redisplay 1
5637         run drawvisible
5638     }
5639 }
5640
5641 proc drawvisible {} {
5642     global canv linespc curview vrowmod selectedline targetrow targetid
5643     global need_redisplay cscroll numcommits
5644
5645     set fs [$canv yview]
5646     set ymax [lindex [$canv cget -scrollregion] 3]
5647     if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5648     set f0 [lindex $fs 0]
5649     set f1 [lindex $fs 1]
5650     set y0 [expr {int($f0 * $ymax)}]
5651     set y1 [expr {int($f1 * $ymax)}]
5652
5653     if {[info exists targetid]} {
5654         if {[commitinview $targetid $curview]} {
5655             set r [rowofcommit $targetid]
5656             if {$r != $targetrow} {
5657                 # Fix up the scrollregion and change the scrolling position
5658                 # now that our target row has moved.
5659                 set diff [expr {($r - $targetrow) * $linespc}]
5660                 set targetrow $r
5661                 setcanvscroll
5662                 set ymax [lindex [$canv cget -scrollregion] 3]
5663                 incr y0 $diff
5664                 incr y1 $diff
5665                 set f0 [expr {$y0 / $ymax}]
5666                 set f1 [expr {$y1 / $ymax}]
5667                 allcanvs yview moveto $f0
5668                 $cscroll set $f0 $f1
5669                 set need_redisplay 1
5670             }
5671         } else {
5672             unset targetid
5673         }
5674     }
5675
5676     set row [expr {int(($y0 - 3) / $linespc) - 1}]
5677     set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5678     if {$endrow >= $vrowmod($curview)} {
5679         update_arcrows $curview
5680     }
5681     if {$selectedline ne {} &&
5682         $row <= $selectedline && $selectedline <= $endrow} {
5683         set targetrow $selectedline
5684     } elseif {[info exists targetid]} {
5685         set targetrow [expr {int(($row + $endrow) / 2)}]
5686     }
5687     if {[info exists targetrow]} {
5688         if {$targetrow >= $numcommits} {
5689             set targetrow [expr {$numcommits - 1}]
5690         }
5691         set targetid [commitonrow $targetrow]
5692     }
5693     drawcommits $row $endrow
5694 }
5695
5696 proc clear_display {} {
5697     global iddrawn linesegs need_redisplay nrows_drawn
5698     global vhighlights fhighlights nhighlights rhighlights
5699     global linehtag linentag linedtag boldrows boldnamerows
5700
5701     allcanvs delete all
5702     catch {unset iddrawn}
5703     catch {unset linesegs}
5704     catch {unset linehtag}
5705     catch {unset linentag}
5706     catch {unset linedtag}
5707     set boldrows {}
5708     set boldnamerows {}
5709     catch {unset vhighlights}
5710     catch {unset fhighlights}
5711     catch {unset nhighlights}
5712     catch {unset rhighlights}
5713     set need_redisplay 0
5714     set nrows_drawn 0
5715 }
5716
5717 proc findcrossings {id} {
5718     global rowidlist parentlist numcommits displayorder
5719
5720     set cross {}
5721     set ccross {}
5722     foreach {s e} [rowranges $id] {
5723         if {$e >= $numcommits} {
5724             set e [expr {$numcommits - 1}]
5725         }
5726         if {$e <= $s} continue
5727         for {set row $e} {[incr row -1] >= $s} {} {
5728             set x [lsearch -exact [lindex $rowidlist $row] $id]
5729             if {$x < 0} break
5730             set olds [lindex $parentlist $row]
5731             set kid [lindex $displayorder $row]
5732             set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5733             if {$kidx < 0} continue
5734             set nextrow [lindex $rowidlist [expr {$row + 1}]]
5735             foreach p $olds {
5736                 set px [lsearch -exact $nextrow $p]
5737                 if {$px < 0} continue
5738                 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5739                     if {[lsearch -exact $ccross $p] >= 0} continue
5740                     if {$x == $px + ($kidx < $px? -1: 1)} {
5741                         lappend ccross $p
5742                     } elseif {[lsearch -exact $cross $p] < 0} {
5743                         lappend cross $p
5744                     }
5745                 }
5746             }
5747         }
5748     }
5749     return [concat $ccross {{}} $cross]
5750 }
5751
5752 proc assigncolor {id} {
5753     global colormap colors nextcolor
5754     global parents children children curview
5755
5756     if {[info exists colormap($id)]} return
5757     set ncolors [llength $colors]
5758     if {[info exists children($curview,$id)]} {
5759         set kids $children($curview,$id)
5760     } else {
5761         set kids {}
5762     }
5763     if {[llength $kids] == 1} {
5764         set child [lindex $kids 0]
5765         if {[info exists colormap($child)]
5766             && [llength $parents($curview,$child)] == 1} {
5767             set colormap($id) $colormap($child)
5768             return
5769         }
5770     }
5771     set badcolors {}
5772     set origbad {}
5773     foreach x [findcrossings $id] {
5774         if {$x eq {}} {
5775             # delimiter between corner crossings and other crossings
5776             if {[llength $badcolors] >= $ncolors - 1} break
5777             set origbad $badcolors
5778         }
5779         if {[info exists colormap($x)]
5780             && [lsearch -exact $badcolors $colormap($x)] < 0} {
5781             lappend badcolors $colormap($x)
5782         }
5783     }
5784     if {[llength $badcolors] >= $ncolors} {
5785         set badcolors $origbad
5786     }
5787     set origbad $badcolors
5788     if {[llength $badcolors] < $ncolors - 1} {
5789         foreach child $kids {
5790             if {[info exists colormap($child)]
5791                 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5792                 lappend badcolors $colormap($child)
5793             }
5794             foreach p $parents($curview,$child) {
5795                 if {[info exists colormap($p)]
5796                     && [lsearch -exact $badcolors $colormap($p)] < 0} {
5797                     lappend badcolors $colormap($p)
5798                 }
5799             }
5800         }
5801         if {[llength $badcolors] >= $ncolors} {
5802             set badcolors $origbad
5803         }
5804     }
5805     for {set i 0} {$i <= $ncolors} {incr i} {
5806         set c [lindex $colors $nextcolor]
5807         if {[incr nextcolor] >= $ncolors} {
5808             set nextcolor 0
5809         }
5810         if {[lsearch -exact $badcolors $c]} break
5811     }
5812     set colormap($id) $c
5813 }
5814
5815 proc bindline {t id} {
5816     global canv
5817
5818     $canv bind $t <Enter> "lineenter %x %y $id"
5819     $canv bind $t <Motion> "linemotion %x %y $id"
5820     $canv bind $t <Leave> "lineleave $id"
5821     $canv bind $t <Button-1> "lineclick %x %y $id 1"
5822 }
5823
5824 proc drawtags {id x xt y1} {
5825     global idtags idheads idotherrefs mainhead
5826     global linespc lthickness
5827     global canv rowtextx curview fgcolor bgcolor ctxbut
5828
5829     set marks {}
5830     set ntags 0
5831     set nheads 0
5832     if {[info exists idtags($id)]} {
5833         set marks $idtags($id)
5834         set ntags [llength $marks]
5835     }
5836     if {[info exists idheads($id)]} {
5837         set marks [concat $marks $idheads($id)]
5838         set nheads [llength $idheads($id)]
5839     }
5840     if {[info exists idotherrefs($id)]} {
5841         set marks [concat $marks $idotherrefs($id)]
5842     }
5843     if {$marks eq {}} {
5844         return $xt
5845     }
5846
5847     set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5848     set yt [expr {$y1 - 0.5 * $linespc}]
5849     set yb [expr {$yt + $linespc - 1}]
5850     set xvals {}
5851     set wvals {}
5852     set i -1
5853     foreach tag $marks {
5854         incr i
5855         if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5856             set wid [font measure mainfontbold $tag]
5857         } else {
5858             set wid [font measure mainfont $tag]
5859         }
5860         lappend xvals $xt
5861         lappend wvals $wid
5862         set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5863     }
5864     set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5865                -width $lthickness -fill black -tags tag.$id]
5866     $canv lower $t
5867     foreach tag $marks x $xvals wid $wvals {
5868         set xl [expr {$x + $delta}]
5869         set xr [expr {$x + $delta + $wid + $lthickness}]
5870         set font mainfont
5871         if {[incr ntags -1] >= 0} {
5872             # draw a tag
5873             set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5874                        $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5875                        -width 1 -outline black -fill yellow -tags tag.$id]
5876             $canv bind $t <1> [list showtag $tag 1]
5877             set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5878         } else {
5879             # draw a head or other ref
5880             if {[incr nheads -1] >= 0} {
5881                 set col green
5882                 if {$tag eq $mainhead} {
5883                     set font mainfontbold
5884                 }
5885             } else {
5886                 set col "#ddddff"
5887             }
5888             set xl [expr {$xl - $delta/2}]
5889             $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5890                 -width 1 -outline black -fill $col -tags tag.$id
5891             if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5892                 set rwid [font measure mainfont $remoteprefix]
5893                 set xi [expr {$x + 1}]
5894                 set yti [expr {$yt + 1}]
5895                 set xri [expr {$x + $rwid}]
5896                 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5897                         -width 0 -fill "#ffddaa" -tags tag.$id
5898             }
5899         }
5900         set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5901                    -font $font -tags [list tag.$id text]]
5902         if {$ntags >= 0} {
5903             $canv bind $t <1> [list showtag $tag 1]
5904         } elseif {$nheads >= 0} {
5905             $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
5906         }
5907     }
5908     return $xt
5909 }
5910
5911 proc xcoord {i level ln} {
5912     global canvx0 xspc1 xspc2
5913
5914     set x [expr {$canvx0 + $i * $xspc1($ln)}]
5915     if {$i > 0 && $i == $level} {
5916         set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5917     } elseif {$i > $level} {
5918         set x [expr {$x + $xspc2 - $xspc1($ln)}]
5919     }
5920     return $x
5921 }
5922
5923 proc show_status {msg} {
5924     global canv fgcolor
5925
5926     clear_display
5927     $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5928         -tags text -fill $fgcolor
5929 }
5930
5931 # Don't change the text pane cursor if it is currently the hand cursor,
5932 # showing that we are over a sha1 ID link.
5933 proc settextcursor {c} {
5934     global ctext curtextcursor
5935
5936     if {[$ctext cget -cursor] == $curtextcursor} {
5937         $ctext config -cursor $c
5938     }
5939     set curtextcursor $c
5940 }
5941
5942 proc nowbusy {what {name {}}} {
5943     global isbusy busyname statusw
5944
5945     if {[array names isbusy] eq {}} {
5946         . config -cursor watch
5947         settextcursor watch
5948     }
5949     set isbusy($what) 1
5950     set busyname($what) $name
5951     if {$name ne {}} {
5952         $statusw conf -text $name
5953     }
5954 }
5955
5956 proc notbusy {what} {
5957     global isbusy maincursor textcursor busyname statusw
5958
5959     catch {
5960         unset isbusy($what)
5961         if {$busyname($what) ne {} &&
5962             [$statusw cget -text] eq $busyname($what)} {
5963             $statusw conf -text {}
5964         }
5965     }
5966     if {[array names isbusy] eq {}} {
5967         . config -cursor $maincursor
5968         settextcursor $textcursor
5969     }
5970 }
5971
5972 proc findmatches {f} {
5973     global findtype findstring
5974     if {$findtype == [mc "Regexp"]} {
5975         set matches [regexp -indices -all -inline $findstring $f]
5976     } else {
5977         set fs $findstring
5978         if {$findtype == [mc "IgnCase"]} {
5979             set f [string tolower $f]
5980             set fs [string tolower $fs]
5981         }
5982         set matches {}
5983         set i 0
5984         set l [string length $fs]
5985         while {[set j [string first $fs $f $i]] >= 0} {
5986             lappend matches [list $j [expr {$j+$l-1}]]
5987             set i [expr {$j + $l}]
5988         }
5989     }
5990     return $matches
5991 }
5992
5993 proc dofind {{dirn 1} {wrap 1}} {
5994     global findstring findstartline findcurline selectedline numcommits
5995     global gdttype filehighlight fh_serial find_dirn findallowwrap
5996
5997     if {[info exists find_dirn]} {
5998         if {$find_dirn == $dirn} return
5999         stopfinding
6000     }
6001     focus .
6002     if {$findstring eq {} || $numcommits == 0} return
6003     if {$selectedline eq {}} {
6004         set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
6005     } else {
6006         set findstartline $selectedline
6007     }
6008     set findcurline $findstartline
6009     nowbusy finding [mc "Searching"]
6010     if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
6011         after cancel do_file_hl $fh_serial
6012         do_file_hl $fh_serial
6013     }
6014     set find_dirn $dirn
6015     set findallowwrap $wrap
6016     run findmore
6017 }
6018
6019 proc stopfinding {} {
6020     global find_dirn findcurline fprogcoord
6021
6022     if {[info exists find_dirn]} {
6023         unset find_dirn
6024         unset findcurline
6025         notbusy finding
6026         set fprogcoord 0
6027         adjustprogress
6028     }
6029     stopblaming
6030 }
6031
6032 proc findmore {} {
6033     global commitdata commitinfo numcommits findpattern findloc
6034     global findstartline findcurline findallowwrap
6035     global find_dirn gdttype fhighlights fprogcoord
6036     global curview varcorder vrownum varccommits vrowmod
6037
6038     if {![info exists find_dirn]} {
6039         return 0
6040     }
6041     set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
6042     set l $findcurline
6043     set moretodo 0
6044     if {$find_dirn > 0} {
6045         incr l
6046         if {$l >= $numcommits} {
6047             set l 0
6048         }
6049         if {$l <= $findstartline} {
6050             set lim [expr {$findstartline + 1}]
6051         } else {
6052             set lim $numcommits
6053             set moretodo $findallowwrap
6054         }
6055     } else {
6056         if {$l == 0} {
6057             set l $numcommits
6058         }
6059         incr l -1
6060         if {$l >= $findstartline} {
6061             set lim [expr {$findstartline - 1}]
6062         } else {
6063             set lim -1
6064             set moretodo $findallowwrap
6065         }
6066     }
6067     set n [expr {($lim - $l) * $find_dirn}]
6068     if {$n > 500} {
6069         set n 500
6070         set moretodo 1
6071     }
6072     if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6073         update_arcrows $curview
6074     }
6075     set found 0
6076     set domore 1
6077     set ai [bsearch $vrownum($curview) $l]
6078     set a [lindex $varcorder($curview) $ai]
6079     set arow [lindex $vrownum($curview) $ai]
6080     set ids [lindex $varccommits($curview,$a)]
6081     set arowend [expr {$arow + [llength $ids]}]
6082     if {$gdttype eq [mc "containing:"]} {
6083         for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6084             if {$l < $arow || $l >= $arowend} {
6085                 incr ai $find_dirn
6086                 set a [lindex $varcorder($curview) $ai]
6087                 set arow [lindex $vrownum($curview) $ai]
6088                 set ids [lindex $varccommits($curview,$a)]
6089                 set arowend [expr {$arow + [llength $ids]}]
6090             }
6091             set id [lindex $ids [expr {$l - $arow}]]
6092             # shouldn't happen unless git log doesn't give all the commits...
6093             if {![info exists commitdata($id)] ||
6094                 ![doesmatch $commitdata($id)]} {
6095                 continue
6096             }
6097             if {![info exists commitinfo($id)]} {
6098                 getcommit $id
6099             }
6100             set info $commitinfo($id)
6101             foreach f $info ty $fldtypes {
6102                 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6103                     [doesmatch $f]} {
6104                     set found 1
6105                     break
6106                 }
6107             }
6108             if {$found} break
6109         }
6110     } else {
6111         for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6112             if {$l < $arow || $l >= $arowend} {
6113                 incr ai $find_dirn
6114                 set a [lindex $varcorder($curview) $ai]
6115                 set arow [lindex $vrownum($curview) $ai]
6116                 set ids [lindex $varccommits($curview,$a)]
6117                 set arowend [expr {$arow + [llength $ids]}]
6118             }
6119             set id [lindex $ids [expr {$l - $arow}]]
6120             if {![info exists fhighlights($id)]} {
6121                 # this sets fhighlights($id) to -1
6122                 askfilehighlight $l $id
6123             }
6124             if {$fhighlights($id) > 0} {
6125                 set found $domore
6126                 break
6127             }
6128             if {$fhighlights($id) < 0} {
6129                 if {$domore} {
6130                     set domore 0
6131                     set findcurline [expr {$l - $find_dirn}]
6132                 }
6133             }
6134         }
6135     }
6136     if {$found || ($domore && !$moretodo)} {
6137         unset findcurline
6138         unset find_dirn
6139         notbusy finding
6140         set fprogcoord 0
6141         adjustprogress
6142         if {$found} {
6143             findselectline $l
6144         } else {
6145             bell
6146         }
6147         return 0
6148     }
6149     if {!$domore} {
6150         flushhighlights
6151     } else {
6152         set findcurline [expr {$l - $find_dirn}]
6153     }
6154     set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6155     if {$n < 0} {
6156         incr n $numcommits
6157     }
6158     set fprogcoord [expr {$n * 1.0 / $numcommits}]
6159     adjustprogress
6160     return $domore
6161 }
6162
6163 proc findselectline {l} {
6164     global findloc commentend ctext findcurline markingmatches gdttype
6165
6166     set markingmatches 1
6167     set findcurline $l
6168     selectline $l 1
6169     if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
6170         # highlight the matches in the comments
6171         set f [$ctext get 1.0 $commentend]
6172         set matches [findmatches $f]
6173         foreach match $matches {
6174             set start [lindex $match 0]
6175             set end [expr {[lindex $match 1] + 1}]
6176             $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6177         }
6178     }
6179     drawvisible
6180 }
6181
6182 # mark the bits of a headline or author that match a find string
6183 proc markmatches {canv l str tag matches font row} {
6184     global selectedline
6185
6186     set bbox [$canv bbox $tag]
6187     set x0 [lindex $bbox 0]
6188     set y0 [lindex $bbox 1]
6189     set y1 [lindex $bbox 3]
6190     foreach match $matches {
6191         set start [lindex $match 0]
6192         set end [lindex $match 1]
6193         if {$start > $end} continue
6194         set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6195         set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6196         set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6197                    [expr {$x0+$xlen+2}] $y1 \
6198                    -outline {} -tags [list match$l matches] -fill yellow]
6199         $canv lower $t
6200         if {$row == $selectedline} {
6201             $canv raise $t secsel
6202         }
6203     }
6204 }
6205
6206 proc unmarkmatches {} {
6207     global markingmatches
6208
6209     allcanvs delete matches
6210     set markingmatches 0
6211     stopfinding
6212 }
6213
6214 proc selcanvline {w x y} {
6215     global canv canvy0 ctext linespc
6216     global rowtextx
6217     set ymax [lindex [$canv cget -scrollregion] 3]
6218     if {$ymax == {}} return
6219     set yfrac [lindex [$canv yview] 0]
6220     set y [expr {$y + $yfrac * $ymax}]
6221     set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6222     if {$l < 0} {
6223         set l 0
6224     }
6225     if {$w eq $canv} {
6226         set xmax [lindex [$canv cget -scrollregion] 2]
6227         set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6228         if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6229     }
6230     unmarkmatches
6231     selectline $l 1
6232 }
6233
6234 proc commit_descriptor {p} {
6235     global commitinfo
6236     if {![info exists commitinfo($p)]} {
6237         getcommit $p
6238     }
6239     set l "..."
6240     if {[llength $commitinfo($p)] > 1} {
6241         set l [lindex $commitinfo($p) 0]
6242     }
6243     return "$p ($l)\n"
6244 }
6245
6246 # append some text to the ctext widget, and make any SHA1 ID
6247 # that we know about be a clickable link.
6248 proc appendwithlinks {text tags} {
6249     global ctext linknum curview
6250
6251     set start [$ctext index "end - 1c"]
6252     $ctext insert end $text $tags
6253     set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6254     foreach l $links {
6255         set s [lindex $l 0]
6256         set e [lindex $l 1]
6257         set linkid [string range $text $s $e]
6258         incr e
6259         $ctext tag delete link$linknum
6260         $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6261         setlink $linkid link$linknum
6262         incr linknum
6263     }
6264 }
6265
6266 proc setlink {id lk} {
6267     global curview ctext pendinglinks
6268
6269     set known 0
6270     if {[string length $id] < 40} {
6271         set matches [longid $id]
6272         if {[llength $matches] > 0} {
6273             if {[llength $matches] > 1} return
6274             set known 1
6275             set id [lindex $matches 0]
6276         }
6277     } else {
6278         set known [commitinview $id $curview]
6279     }
6280     if {$known} {
6281         $ctext tag conf $lk -foreground blue -underline 1
6282         $ctext tag bind $lk <1> [list selbyid $id]
6283         $ctext tag bind $lk <Enter> {linkcursor %W 1}
6284         $ctext tag bind $lk <Leave> {linkcursor %W -1}
6285     } else {
6286         lappend pendinglinks($id) $lk
6287         interestedin $id {makelink %P}
6288     }
6289 }
6290
6291 proc makelink {id} {
6292     global pendinglinks
6293
6294     if {![info exists pendinglinks($id)]} return
6295     foreach lk $pendinglinks($id) {
6296         setlink $id $lk
6297     }
6298     unset pendinglinks($id)
6299 }
6300
6301 proc linkcursor {w inc} {
6302     global linkentercount curtextcursor
6303
6304     if {[incr linkentercount $inc] > 0} {
6305         $w configure -cursor hand2
6306     } else {
6307         $w configure -cursor $curtextcursor
6308         if {$linkentercount < 0} {
6309             set linkentercount 0
6310         }
6311     }
6312 }
6313
6314 proc viewnextline {dir} {
6315     global canv linespc
6316
6317     $canv delete hover
6318     set ymax [lindex [$canv cget -scrollregion] 3]
6319     set wnow [$canv yview]
6320     set wtop [expr {[lindex $wnow 0] * $ymax}]
6321     set newtop [expr {$wtop + $dir * $linespc}]
6322     if {$newtop < 0} {
6323         set newtop 0
6324     } elseif {$newtop > $ymax} {
6325         set newtop $ymax
6326     }
6327     allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6328 }
6329
6330 # add a list of tag or branch names at position pos
6331 # returns the number of names inserted
6332 proc appendrefs {pos ids var} {
6333     global ctext linknum curview $var maxrefs
6334
6335     if {[catch {$ctext index $pos}]} {
6336         return 0
6337     }
6338     $ctext conf -state normal
6339     $ctext delete $pos "$pos lineend"
6340     set tags {}
6341     foreach id $ids {
6342         foreach tag [set $var\($id\)] {
6343             lappend tags [list $tag $id]
6344         }
6345     }
6346     if {[llength $tags] > $maxrefs} {
6347         $ctext insert $pos "many ([llength $tags])"
6348     } else {
6349         set tags [lsort -index 0 -decreasing $tags]
6350         set sep {}
6351         foreach ti $tags {
6352             set id [lindex $ti 1]
6353             set lk link$linknum
6354             incr linknum
6355             $ctext tag delete $lk
6356             $ctext insert $pos $sep
6357             $ctext insert $pos [lindex $ti 0] $lk
6358             setlink $id $lk
6359             set sep ", "
6360         }
6361     }
6362     $ctext conf -state disabled
6363     return [llength $tags]
6364 }
6365
6366 # called when we have finished computing the nearby tags
6367 proc dispneartags {delay} {
6368     global selectedline currentid showneartags tagphase
6369
6370     if {$selectedline eq {} || !$showneartags} return
6371     after cancel dispnexttag
6372     if {$delay} {
6373         after 200 dispnexttag
6374         set tagphase -1
6375     } else {
6376         after idle dispnexttag
6377         set tagphase 0
6378     }
6379 }
6380
6381 proc dispnexttag {} {
6382     global selectedline currentid showneartags tagphase ctext
6383
6384     if {$selectedline eq {} || !$showneartags} return
6385     switch -- $tagphase {
6386         0 {
6387             set dtags [desctags $currentid]
6388             if {$dtags ne {}} {
6389                 appendrefs precedes $dtags idtags
6390             }
6391         }
6392         1 {
6393             set atags [anctags $currentid]
6394             if {$atags ne {}} {
6395                 appendrefs follows $atags idtags
6396             }
6397         }
6398         2 {
6399             set dheads [descheads $currentid]
6400             if {$dheads ne {}} {
6401                 if {[appendrefs branch $dheads idheads] > 1
6402                     && [$ctext get "branch -3c"] eq "h"} {
6403                     # turn "Branch" into "Branches"
6404                     $ctext conf -state normal
6405                     $ctext insert "branch -2c" "es"
6406                     $ctext conf -state disabled
6407                 }
6408             }
6409         }
6410     }
6411     if {[incr tagphase] <= 2} {
6412         after idle dispnexttag
6413     }
6414 }
6415
6416 proc make_secsel {l} {
6417     global linehtag linentag linedtag canv canv2 canv3
6418
6419     if {![info exists linehtag($l)]} return
6420     $canv delete secsel
6421     set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
6422                -tags secsel -fill [$canv cget -selectbackground]]
6423     $canv lower $t
6424     $canv2 delete secsel
6425     set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
6426                -tags secsel -fill [$canv2 cget -selectbackground]]
6427     $canv2 lower $t
6428     $canv3 delete secsel
6429     set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
6430                -tags secsel -fill [$canv3 cget -selectbackground]]
6431     $canv3 lower $t
6432 }
6433
6434 proc selectline {l isnew {desired_loc {}}} {
6435     global canv ctext commitinfo selectedline
6436     global canvy0 linespc parents children curview
6437     global currentid sha1entry
6438     global commentend idtags linknum
6439     global mergemax numcommits pending_select
6440     global cmitmode showneartags allcommits
6441     global targetrow targetid lastscrollrows
6442     global autoselect jump_to_here
6443
6444     catch {unset pending_select}
6445     $canv delete hover
6446     normalline
6447     unsel_reflist
6448     stopfinding
6449     if {$l < 0 || $l >= $numcommits} return
6450     set id [commitonrow $l]
6451     set targetid $id
6452     set targetrow $l
6453     set selectedline $l
6454     set currentid $id
6455     if {$lastscrollrows < $numcommits} {
6456         setcanvscroll
6457     }
6458
6459     set y [expr {$canvy0 + $l * $linespc}]
6460     set ymax [lindex [$canv cget -scrollregion] 3]
6461     set ytop [expr {$y - $linespc - 1}]
6462     set ybot [expr {$y + $linespc + 1}]
6463     set wnow [$canv yview]
6464     set wtop [expr {[lindex $wnow 0] * $ymax}]
6465     set wbot [expr {[lindex $wnow 1] * $ymax}]
6466     set wh [expr {$wbot - $wtop}]
6467     set newtop $wtop
6468     if {$ytop < $wtop} {
6469         if {$ybot < $wtop} {
6470             set newtop [expr {$y - $wh / 2.0}]
6471         } else {
6472             set newtop $ytop
6473             if {$newtop > $wtop - $linespc} {
6474                 set newtop [expr {$wtop - $linespc}]
6475             }
6476         }
6477     } elseif {$ybot > $wbot} {
6478         if {$ytop > $wbot} {
6479             set newtop [expr {$y - $wh / 2.0}]
6480         } else {
6481             set newtop [expr {$ybot - $wh}]
6482             if {$newtop < $wtop + $linespc} {
6483                 set newtop [expr {$wtop + $linespc}]
6484             }
6485         }
6486     }
6487     if {$newtop != $wtop} {
6488         if {$newtop < 0} {
6489             set newtop 0
6490         }
6491         allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6492         drawvisible
6493     }
6494
6495     make_secsel $l
6496
6497     if {$isnew} {
6498         addtohistory [list selbyid $id]
6499     }
6500
6501     $sha1entry delete 0 end
6502     $sha1entry insert 0 $id
6503     if {$autoselect} {
6504         $sha1entry selection from 0
6505         $sha1entry selection to end
6506     }
6507     rhighlight_sel $id
6508
6509     $ctext conf -state normal
6510     clear_ctext
6511     set linknum 0
6512     if {![info exists commitinfo($id)]} {
6513         getcommit $id
6514     }
6515     set info $commitinfo($id)
6516     set date [formatdate [lindex $info 2]]
6517     $ctext insert end "[mc "Author"]: [lindex $info 1]  $date\n"
6518     set date [formatdate [lindex $info 4]]
6519     $ctext insert end "[mc "Committer"]: [lindex $info 3]  $date\n"
6520     if {[info exists idtags($id)]} {
6521         $ctext insert end [mc "Tags:"]
6522         foreach tag $idtags($id) {
6523             $ctext insert end " $tag"
6524         }
6525         $ctext insert end "\n"
6526     }
6527
6528     set headers {}
6529     set olds $parents($curview,$id)
6530     if {[llength $olds] > 1} {
6531         set np 0
6532         foreach p $olds {
6533             if {$np >= $mergemax} {
6534                 set tag mmax
6535             } else {
6536                 set tag m$np
6537             }
6538             $ctext insert end "[mc "Parent"]: " $tag
6539             appendwithlinks [commit_descriptor $p] {}
6540             incr np
6541         }
6542     } else {
6543         foreach p $olds {
6544             append headers "[mc "Parent"]: [commit_descriptor $p]"
6545         }
6546     }
6547
6548     foreach c $children($curview,$id) {
6549         append headers "[mc "Child"]:  [commit_descriptor $c]"
6550     }
6551
6552     # make anything that looks like a SHA1 ID be a clickable link
6553     appendwithlinks $headers {}
6554     if {$showneartags} {
6555         if {![info exists allcommits]} {
6556             getallcommits
6557         }
6558         $ctext insert end "[mc "Branch"]: "
6559         $ctext mark set branch "end -1c"
6560         $ctext mark gravity branch left
6561         $ctext insert end "\n[mc "Follows"]: "
6562         $ctext mark set follows "end -1c"
6563         $ctext mark gravity follows left
6564         $ctext insert end "\n[mc "Precedes"]: "
6565         $ctext mark set precedes "end -1c"
6566         $ctext mark gravity precedes left
6567         $ctext insert end "\n"
6568         dispneartags 1
6569     }
6570     $ctext insert end "\n"
6571     set comment [lindex $info 5]
6572     if {[string first "\r" $comment] >= 0} {
6573         set comment [string map {"\r" "\n    "} $comment]
6574     }
6575     appendwithlinks $comment {comment}
6576
6577     $ctext tag remove found 1.0 end
6578     $ctext conf -state disabled
6579     set commentend [$ctext index "end - 1c"]
6580
6581     set jump_to_here $desired_loc
6582     init_flist [mc "Comments"]
6583     if {$cmitmode eq "tree"} {
6584         gettree $id
6585     } elseif {[llength $olds] <= 1} {
6586         startdiff $id
6587     } else {
6588         mergediff $id
6589     }
6590 }
6591
6592 proc selfirstline {} {
6593     unmarkmatches
6594     selectline 0 1
6595 }
6596
6597 proc sellastline {} {
6598     global numcommits
6599     unmarkmatches
6600     set l [expr {$numcommits - 1}]
6601     selectline $l 1
6602 }
6603
6604 proc selnextline {dir} {
6605     global selectedline
6606     focus .
6607     if {$selectedline eq {}} return
6608     set l [expr {$selectedline + $dir}]
6609     unmarkmatches
6610     selectline $l 1
6611 }
6612
6613 proc selnextpage {dir} {
6614     global canv linespc selectedline numcommits
6615
6616     set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6617     if {$lpp < 1} {
6618         set lpp 1
6619     }
6620     allcanvs yview scroll [expr {$dir * $lpp}] units
6621     drawvisible
6622     if {$selectedline eq {}} return
6623     set l [expr {$selectedline + $dir * $lpp}]
6624     if {$l < 0} {
6625         set l 0
6626     } elseif {$l >= $numcommits} {
6627         set l [expr $numcommits - 1]
6628     }
6629     unmarkmatches
6630     selectline $l 1
6631 }
6632
6633 proc unselectline {} {
6634     global selectedline currentid
6635
6636     set selectedline {}
6637     catch {unset currentid}
6638     allcanvs delete secsel
6639     rhighlight_none
6640 }
6641
6642 proc reselectline {} {
6643     global selectedline
6644
6645     if {$selectedline ne {}} {
6646         selectline $selectedline 0
6647     }
6648 }
6649
6650 proc addtohistory {cmd} {
6651     global history historyindex curview
6652
6653     set elt [list $curview $cmd]
6654     if {$historyindex > 0
6655         && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6656         return
6657     }
6658
6659     if {$historyindex < [llength $history]} {
6660         set history [lreplace $history $historyindex end $elt]
6661     } else {
6662         lappend history $elt
6663     }
6664     incr historyindex
6665     if {$historyindex > 1} {
6666         .tf.bar.leftbut conf -state normal
6667     } else {
6668         .tf.bar.leftbut conf -state disabled
6669     }
6670     .tf.bar.rightbut conf -state disabled
6671 }
6672
6673 proc godo {elt} {
6674     global curview
6675
6676     set view [lindex $elt 0]
6677     set cmd [lindex $elt 1]
6678     if {$curview != $view} {
6679         showview $view
6680     }
6681     eval $cmd
6682 }
6683
6684 proc goback {} {
6685     global history historyindex
6686     focus .
6687
6688     if {$historyindex > 1} {
6689         incr historyindex -1
6690         godo [lindex $history [expr {$historyindex - 1}]]
6691         .tf.bar.rightbut conf -state normal
6692     }
6693     if {$historyindex <= 1} {
6694         .tf.bar.leftbut conf -state disabled
6695     }
6696 }
6697
6698 proc goforw {} {
6699     global history historyindex
6700     focus .
6701
6702     if {$historyindex < [llength $history]} {
6703         set cmd [lindex $history $historyindex]
6704         incr historyindex
6705         godo $cmd
6706         .tf.bar.leftbut conf -state normal
6707     }
6708     if {$historyindex >= [llength $history]} {
6709         .tf.bar.rightbut conf -state disabled
6710     }
6711 }
6712
6713 proc gettree {id} {
6714     global treefilelist treeidlist diffids diffmergeid treepending
6715     global nullid nullid2
6716
6717     set diffids $id
6718     catch {unset diffmergeid}
6719     if {![info exists treefilelist($id)]} {
6720         if {![info exists treepending]} {
6721             if {$id eq $nullid} {
6722                 set cmd [list | git ls-files]
6723             } elseif {$id eq $nullid2} {
6724                 set cmd [list | git ls-files --stage -t]
6725             } else {
6726                 set cmd [list | git ls-tree -r $id]
6727             }
6728             if {[catch {set gtf [open $cmd r]}]} {
6729                 return
6730             }
6731             set treepending $id
6732             set treefilelist($id) {}
6733             set treeidlist($id) {}
6734             fconfigure $gtf -blocking 0 -encoding binary
6735             filerun $gtf [list gettreeline $gtf $id]
6736         }
6737     } else {
6738         setfilelist $id
6739     }
6740 }
6741
6742 proc gettreeline {gtf id} {
6743     global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6744
6745     set nl 0
6746     while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6747         if {$diffids eq $nullid} {
6748             set fname $line
6749         } else {
6750             set i [string first "\t" $line]
6751             if {$i < 0} continue
6752             set fname [string range $line [expr {$i+1}] end]
6753             set line [string range $line 0 [expr {$i-1}]]
6754             if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6755             set sha1 [lindex $line 2]
6756             lappend treeidlist($id) $sha1
6757         }
6758         if {[string index $fname 0] eq "\""} {
6759             set fname [lindex $fname 0]
6760         }
6761         set fname [encoding convertfrom $fname]
6762         lappend treefilelist($id) $fname
6763     }
6764     if {![eof $gtf]} {
6765         return [expr {$nl >= 1000? 2: 1}]
6766     }
6767     close $gtf
6768     unset treepending
6769     if {$cmitmode ne "tree"} {
6770         if {![info exists diffmergeid]} {
6771             gettreediffs $diffids
6772         }
6773     } elseif {$id ne $diffids} {
6774         gettree $diffids
6775     } else {
6776         setfilelist $id
6777     }
6778     return 0
6779 }
6780
6781 proc showfile {f} {
6782     global treefilelist treeidlist diffids nullid nullid2
6783     global ctext_file_names ctext_file_lines
6784     global ctext commentend
6785
6786     set i [lsearch -exact $treefilelist($diffids) $f]
6787     if {$i < 0} {
6788         puts "oops, $f not in list for id $diffids"
6789         return
6790     }
6791     if {$diffids eq $nullid} {
6792         if {[catch {set bf [open $f r]} err]} {
6793             puts "oops, can't read $f: $err"
6794             return
6795         }
6796     } else {
6797         set blob [lindex $treeidlist($diffids) $i]
6798         if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6799             puts "oops, error reading blob $blob: $err"
6800             return
6801         }
6802     }
6803     fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
6804     filerun $bf [list getblobline $bf $diffids]
6805     $ctext config -state normal
6806     clear_ctext $commentend
6807     lappend ctext_file_names $f
6808     lappend ctext_file_lines [lindex [split $commentend "."] 0]
6809     $ctext insert end "\n"
6810     $ctext insert end "$f\n" filesep
6811     $ctext config -state disabled
6812     $ctext yview $commentend
6813     settabs 0
6814 }
6815
6816 proc getblobline {bf id} {
6817     global diffids cmitmode ctext
6818
6819     if {$id ne $diffids || $cmitmode ne "tree"} {
6820         catch {close $bf}
6821         return 0
6822     }
6823     $ctext config -state normal
6824     set nl 0
6825     while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6826         $ctext insert end "$line\n"
6827     }
6828     if {[eof $bf]} {
6829         global jump_to_here ctext_file_names commentend
6830
6831         # delete last newline
6832         $ctext delete "end - 2c" "end - 1c"
6833         close $bf
6834         if {$jump_to_here ne {} &&
6835             [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
6836             set lnum [expr {[lindex $jump_to_here 1] +
6837                             [lindex [split $commentend .] 0]}]
6838             mark_ctext_line $lnum
6839         }
6840         return 0
6841     }
6842     $ctext config -state disabled
6843     return [expr {$nl >= 1000? 2: 1}]
6844 }
6845
6846 proc mark_ctext_line {lnum} {
6847     global ctext markbgcolor
6848
6849     $ctext tag delete omark
6850     $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
6851     $ctext tag conf omark -background $markbgcolor
6852     $ctext see $lnum.0
6853 }
6854
6855 proc mergediff {id} {
6856     global diffmergeid
6857     global diffids treediffs
6858     global parents curview
6859
6860     set diffmergeid $id
6861     set diffids $id
6862     set treediffs($id) {}
6863     set np [llength $parents($curview,$id)]
6864     settabs $np
6865     getblobdiffs $id
6866 }
6867
6868 proc startdiff {ids} {
6869     global treediffs diffids treepending diffmergeid nullid nullid2
6870
6871     settabs 1
6872     set diffids $ids
6873     catch {unset diffmergeid}
6874     if {![info exists treediffs($ids)] ||
6875         [lsearch -exact $ids $nullid] >= 0 ||
6876         [lsearch -exact $ids $nullid2] >= 0} {
6877         if {![info exists treepending]} {
6878             gettreediffs $ids
6879         }
6880     } else {
6881         addtocflist $ids
6882     }
6883 }
6884
6885 proc path_filter {filter name} {
6886     foreach p $filter {
6887         set l [string length $p]
6888         if {[string index $p end] eq "/"} {
6889             if {[string compare -length $l $p $name] == 0} {
6890                 return 1
6891             }
6892         } else {
6893             if {[string compare -length $l $p $name] == 0 &&
6894                 ([string length $name] == $l ||
6895                  [string index $name $l] eq "/")} {
6896                 return 1
6897             }
6898         }
6899     }
6900     return 0
6901 }
6902
6903 proc addtocflist {ids} {
6904     global treediffs
6905
6906     add_flist $treediffs($ids)
6907     getblobdiffs $ids
6908 }
6909
6910 proc diffcmd {ids flags} {
6911     global nullid nullid2
6912
6913     set i [lsearch -exact $ids $nullid]
6914     set j [lsearch -exact $ids $nullid2]
6915     if {$i >= 0} {
6916         if {[llength $ids] > 1 && $j < 0} {
6917             # comparing working directory with some specific revision
6918             set cmd [concat | git diff-index $flags]
6919             if {$i == 0} {
6920                 lappend cmd -R [lindex $ids 1]
6921             } else {
6922                 lappend cmd [lindex $ids 0]
6923             }
6924         } else {
6925             # comparing working directory with index
6926             set cmd [concat | git diff-files $flags]
6927             if {$j == 1} {
6928                 lappend cmd -R
6929             }
6930         }
6931     } elseif {$j >= 0} {
6932         set cmd [concat | git diff-index --cached $flags]
6933         if {[llength $ids] > 1} {
6934             # comparing index with specific revision
6935             if {$i == 0} {
6936                 lappend cmd -R [lindex $ids 1]
6937             } else {
6938                 lappend cmd [lindex $ids 0]
6939             }
6940         } else {
6941             # comparing index with HEAD
6942             lappend cmd HEAD
6943         }
6944     } else {
6945         set cmd [concat | git diff-tree -r $flags $ids]
6946     }
6947     return $cmd
6948 }
6949
6950 proc gettreediffs {ids} {
6951     global treediff treepending
6952
6953     if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6954
6955     set treepending $ids
6956     set treediff {}
6957     fconfigure $gdtf -blocking 0 -encoding binary
6958     filerun $gdtf [list gettreediffline $gdtf $ids]
6959 }
6960
6961 proc gettreediffline {gdtf ids} {
6962     global treediff treediffs treepending diffids diffmergeid
6963     global cmitmode vfilelimit curview limitdiffs perfile_attrs
6964
6965     set nr 0
6966     set sublist {}
6967     set max 1000
6968     if {$perfile_attrs} {
6969         # cache_gitattr is slow, and even slower on win32 where we
6970         # have to invoke it for only about 30 paths at a time
6971         set max 500
6972         if {[tk windowingsystem] == "win32"} {
6973             set max 120
6974         }
6975     }
6976     while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
6977         set i [string first "\t" $line]
6978         if {$i >= 0} {
6979             set file [string range $line [expr {$i+1}] end]
6980             if {[string index $file 0] eq "\""} {
6981                 set file [lindex $file 0]
6982             }
6983             set file [encoding convertfrom $file]
6984             if {$file ne [lindex $treediff end]} {
6985                 lappend treediff $file
6986                 lappend sublist $file
6987             }
6988         }
6989     }
6990     if {$perfile_attrs} {
6991         cache_gitattr encoding $sublist
6992     }
6993     if {![eof $gdtf]} {
6994         return [expr {$nr >= $max? 2: 1}]
6995     }
6996     close $gdtf
6997     if {$limitdiffs && $vfilelimit($curview) ne {}} {
6998         set flist {}
6999         foreach f $treediff {
7000             if {[path_filter $vfilelimit($curview) $f]} {
7001                 lappend flist $f
7002             }
7003         }
7004         set treediffs($ids) $flist
7005     } else {
7006         set treediffs($ids) $treediff
7007     }
7008     unset treepending
7009     if {$cmitmode eq "tree"} {
7010         gettree $diffids
7011     } elseif {$ids != $diffids} {
7012         if {![info exists diffmergeid]} {
7013             gettreediffs $diffids
7014         }
7015     } else {
7016         addtocflist $ids
7017     }
7018     return 0
7019 }
7020
7021 # empty string or positive integer
7022 proc diffcontextvalidate {v} {
7023     return [regexp {^(|[1-9][0-9]*)$} $v]
7024 }
7025
7026 proc diffcontextchange {n1 n2 op} {
7027     global diffcontextstring diffcontext
7028
7029     if {[string is integer -strict $diffcontextstring]} {
7030         if {$diffcontextstring > 0} {
7031             set diffcontext $diffcontextstring
7032             reselectline
7033         }
7034     }
7035 }
7036
7037 proc changeignorespace {} {
7038     reselectline
7039 }
7040
7041 proc getblobdiffs {ids} {
7042     global blobdifffd diffids env
7043     global diffinhdr treediffs
7044     global diffcontext
7045     global ignorespace
7046     global limitdiffs vfilelimit curview
7047     global diffencoding targetline diffnparents
7048
7049     set cmd [diffcmd $ids "-p -C --cc --no-commit-id -U$diffcontext"]
7050     if {$ignorespace} {
7051         append cmd " -w"
7052     }
7053     if {$limitdiffs && $vfilelimit($curview) ne {}} {
7054         set cmd [concat $cmd -- $vfilelimit($curview)]
7055     }
7056     if {[catch {set bdf [open $cmd r]} err]} {
7057         error_popup [mc "Error getting diffs: %s" $err]
7058         return
7059     }
7060     set targetline {}
7061     set diffnparents 0
7062     set diffinhdr 0
7063     set diffencoding [get_path_encoding {}]
7064     fconfigure $bdf -blocking 0 -encoding binary
7065     set blobdifffd($ids) $bdf
7066     filerun $bdf [list getblobdiffline $bdf $diffids]
7067 }
7068
7069 proc setinlist {var i val} {
7070     global $var
7071
7072     while {[llength [set $var]] < $i} {
7073         lappend $var {}
7074     }
7075     if {[llength [set $var]] == $i} {
7076         lappend $var $val
7077     } else {
7078         lset $var $i $val
7079     }
7080 }
7081
7082 proc makediffhdr {fname ids} {
7083     global ctext curdiffstart treediffs diffencoding
7084     global ctext_file_names jump_to_here targetline diffline
7085
7086     set fname [encoding convertfrom $fname]
7087     set diffencoding [get_path_encoding $fname]
7088     set i [lsearch -exact $treediffs($ids) $fname]
7089     if {$i >= 0} {
7090         setinlist difffilestart $i $curdiffstart
7091     }
7092     lset ctext_file_names end $fname
7093     set l [expr {(78 - [string length $fname]) / 2}]
7094     set pad [string range "----------------------------------------" 1 $l]
7095     $ctext insert $curdiffstart "$pad $fname $pad" filesep
7096     set targetline {}
7097     if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7098         set targetline [lindex $jump_to_here 1]
7099     }
7100     set diffline 0
7101 }
7102
7103 proc getblobdiffline {bdf ids} {
7104     global diffids blobdifffd ctext curdiffstart
7105     global diffnexthead diffnextnote difffilestart
7106     global ctext_file_names ctext_file_lines
7107     global diffinhdr treediffs mergemax diffnparents
7108     global diffencoding jump_to_here targetline diffline
7109
7110     set nr 0
7111     $ctext conf -state normal
7112     while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7113         if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7114             close $bdf
7115             return 0
7116         }
7117         if {![string compare -length 5 "diff " $line]} {
7118             if {![regexp {^diff (--cc|--git) } $line m type]} {
7119                 set line [encoding convertfrom $line]
7120                 $ctext insert end "$line\n" hunksep
7121                 continue
7122             }
7123             # start of a new file
7124             set diffinhdr 1
7125             $ctext insert end "\n"
7126             set curdiffstart [$ctext index "end - 1c"]
7127             lappend ctext_file_names ""
7128             lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7129             $ctext insert end "\n" filesep
7130
7131             if {$type eq "--cc"} {
7132                 # start of a new file in a merge diff
7133                 set fname [string range $line 10 end]
7134                 if {[lsearch -exact $treediffs($ids) $fname] < 0} {
7135                     lappend treediffs($ids) $fname
7136                     add_flist [list $fname]
7137                 }
7138
7139             } else {
7140                 set line [string range $line 11 end]
7141                 # If the name hasn't changed the length will be odd,
7142                 # the middle char will be a space, and the two bits either
7143                 # side will be a/name and b/name, or "a/name" and "b/name".
7144                 # If the name has changed we'll get "rename from" and
7145                 # "rename to" or "copy from" and "copy to" lines following
7146                 # this, and we'll use them to get the filenames.
7147                 # This complexity is necessary because spaces in the
7148                 # filename(s) don't get escaped.
7149                 set l [string length $line]
7150                 set i [expr {$l / 2}]
7151                 if {!(($l & 1) && [string index $line $i] eq " " &&
7152                       [string range $line 2 [expr {$i - 1}]] eq \
7153                           [string range $line [expr {$i + 3}] end])} {
7154                     continue
7155                 }
7156                 # unescape if quoted and chop off the a/ from the front
7157                 if {[string index $line 0] eq "\""} {
7158                     set fname [string range [lindex $line 0] 2 end]
7159                 } else {
7160                     set fname [string range $line 2 [expr {$i - 1}]]
7161                 }
7162             }
7163             makediffhdr $fname $ids
7164
7165         } elseif {![string compare -length 16 "* Unmerged path " $line]} {
7166             set fname [encoding convertfrom [string range $line 16 end]]
7167             $ctext insert end "\n"
7168             set curdiffstart [$ctext index "end - 1c"]
7169             lappend ctext_file_names $fname
7170             lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7171             $ctext insert end "$line\n" filesep
7172             set i [lsearch -exact $treediffs($ids) $fname]
7173             if {$i >= 0} {
7174                 setinlist difffilestart $i $curdiffstart
7175             }
7176
7177         } elseif {![string compare -length 2 "@@" $line]} {
7178             regexp {^@@+} $line ats
7179             set line [encoding convertfrom $diffencoding $line]
7180             $ctext insert end "$line\n" hunksep
7181             if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
7182                 set diffline $nl
7183             }
7184             set diffnparents [expr {[string length $ats] - 1}]
7185             set diffinhdr 0
7186
7187         } elseif {$diffinhdr} {
7188             if {![string compare -length 12 "rename from " $line]} {
7189                 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7190                 if {[string index $fname 0] eq "\""} {
7191                     set fname [lindex $fname 0]
7192                 }
7193                 set fname [encoding convertfrom $fname]
7194                 set i [lsearch -exact $treediffs($ids) $fname]
7195                 if {$i >= 0} {
7196                     setinlist difffilestart $i $curdiffstart
7197                 }
7198             } elseif {![string compare -length 10 $line "rename to "] ||
7199                       ![string compare -length 8 $line "copy to "]} {
7200                 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7201                 if {[string index $fname 0] eq "\""} {
7202                     set fname [lindex $fname 0]
7203                 }
7204                 makediffhdr $fname $ids
7205             } elseif {[string compare -length 3 $line "---"] == 0} {
7206                 # do nothing
7207                 continue
7208             } elseif {[string compare -length 3 $line "+++"] == 0} {
7209                 set diffinhdr 0
7210                 continue
7211             }
7212             $ctext insert end "$line\n" filesep
7213
7214         } else {
7215             set line [encoding convertfrom $diffencoding $line]
7216             # parse the prefix - one ' ', '-' or '+' for each parent
7217             set prefix [string range $line 0 [expr {$diffnparents - 1}]]
7218             set tag [expr {$diffnparents > 1? "m": "d"}]
7219             if {[string trim $prefix " -+"] eq {}} {
7220                 # prefix only has " ", "-" and "+" in it: normal diff line
7221                 set num [string first "-" $prefix]
7222                 if {$num >= 0} {
7223                     # removed line, first parent with line is $num
7224                     if {$num >= $mergemax} {
7225                         set num "max"
7226                     }
7227                     $ctext insert end "$line\n" $tag$num
7228                 } else {
7229                     set tags {}
7230                     if {[string first "+" $prefix] >= 0} {
7231                         # added line
7232                         lappend tags ${tag}result
7233                         if {$diffnparents > 1} {
7234                             set num [string first " " $prefix]
7235                             if {$num >= 0} {
7236                                 if {$num >= $mergemax} {
7237                                     set num "max"
7238                                 }
7239                                 lappend tags m$num
7240                             }
7241                         }
7242                     }
7243                     if {$targetline ne {}} {
7244                         if {$diffline == $targetline} {
7245                             set seehere [$ctext index "end - 1 chars"]
7246                             set targetline {}
7247                         } else {
7248                             incr diffline
7249                         }
7250                     }
7251                     $ctext insert end "$line\n" $tags
7252                 }
7253             } else {
7254                 # "\ No newline at end of file",
7255                 # or something else we don't recognize
7256                 $ctext insert end "$line\n" hunksep
7257             }
7258         }
7259     }
7260     if {[info exists seehere]} {
7261         mark_ctext_line [lindex [split $seehere .] 0]
7262     }
7263     $ctext conf -state disabled
7264     if {[eof $bdf]} {
7265         close $bdf
7266         return 0
7267     }
7268     return [expr {$nr >= 1000? 2: 1}]
7269 }
7270
7271 proc changediffdisp {} {
7272     global ctext diffelide
7273
7274     $ctext tag conf d0 -elide [lindex $diffelide 0]
7275     $ctext tag conf dresult -elide [lindex $diffelide 1]
7276 }
7277
7278 proc highlightfile {loc cline} {
7279     global ctext cflist cflist_top
7280
7281     $ctext yview $loc
7282     $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7283     $cflist tag add highlight $cline.0 "$cline.0 lineend"
7284     $cflist see $cline.0
7285     set cflist_top $cline
7286 }
7287
7288 proc prevfile {} {
7289     global difffilestart ctext cmitmode
7290
7291     if {$cmitmode eq "tree"} return
7292     set prev 0.0
7293     set prevline 1
7294     set here [$ctext index @0,0]
7295     foreach loc $difffilestart {
7296         if {[$ctext compare $loc >= $here]} {
7297             highlightfile $prev $prevline
7298             return
7299         }
7300         set prev $loc
7301         incr prevline
7302     }
7303     highlightfile $prev $prevline
7304 }
7305
7306 proc nextfile {} {
7307     global difffilestart ctext cmitmode
7308
7309     if {$cmitmode eq "tree"} return
7310     set here [$ctext index @0,0]
7311     set line 1
7312     foreach loc $difffilestart {
7313         incr line
7314         if {[$ctext compare $loc > $here]} {
7315             highlightfile $loc $line
7316             return
7317         }
7318     }
7319 }
7320
7321 proc clear_ctext {{first 1.0}} {
7322     global ctext smarktop smarkbot
7323     global ctext_file_names ctext_file_lines
7324     global pendinglinks
7325
7326     set l [lindex [split $first .] 0]
7327     if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
7328         set smarktop $l
7329     }
7330     if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
7331         set smarkbot $l
7332     }
7333     $ctext delete $first end
7334     if {$first eq "1.0"} {
7335         catch {unset pendinglinks}
7336     }
7337     set ctext_file_names {}
7338     set ctext_file_lines {}
7339 }
7340
7341 proc settabs {{firstab {}}} {
7342     global firsttabstop tabstop ctext have_tk85
7343
7344     if {$firstab ne {} && $have_tk85} {
7345         set firsttabstop $firstab
7346     }
7347     set w [font measure textfont "0"]
7348     if {$firsttabstop != 0} {
7349         $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
7350                                [expr {($firsttabstop + 2 * $tabstop) * $w}]]
7351     } elseif {$have_tk85 || $tabstop != 8} {
7352         $ctext conf -tabs [expr {$tabstop * $w}]
7353     } else {
7354         $ctext conf -tabs {}
7355     }
7356 }
7357
7358 proc incrsearch {name ix op} {
7359     global ctext searchstring searchdirn
7360
7361     $ctext tag remove found 1.0 end
7362     if {[catch {$ctext index anchor}]} {
7363         # no anchor set, use start of selection, or of visible area
7364         set sel [$ctext tag ranges sel]
7365         if {$sel ne {}} {
7366             $ctext mark set anchor [lindex $sel 0]
7367         } elseif {$searchdirn eq "-forwards"} {
7368             $ctext mark set anchor @0,0
7369         } else {
7370             $ctext mark set anchor @0,[winfo height $ctext]
7371         }
7372     }
7373     if {$searchstring ne {}} {
7374         set here [$ctext search $searchdirn -- $searchstring anchor]
7375         if {$here ne {}} {
7376             $ctext see $here
7377         }
7378         searchmarkvisible 1
7379     }
7380 }
7381
7382 proc dosearch {} {
7383     global sstring ctext searchstring searchdirn
7384
7385     focus $sstring
7386     $sstring icursor end
7387     set searchdirn -forwards
7388     if {$searchstring ne {}} {
7389         set sel [$ctext tag ranges sel]
7390         if {$sel ne {}} {
7391             set start "[lindex $sel 0] + 1c"
7392         } elseif {[catch {set start [$ctext index anchor]}]} {
7393             set start "@0,0"
7394         }
7395         set match [$ctext search -count mlen -- $searchstring $start]
7396         $ctext tag remove sel 1.0 end
7397         if {$match eq {}} {
7398             bell
7399             return
7400         }
7401         $ctext see $match
7402         set mend "$match + $mlen c"
7403         $ctext tag add sel $match $mend
7404         $ctext mark unset anchor
7405     }
7406 }
7407
7408 proc dosearchback {} {
7409     global sstring ctext searchstring searchdirn
7410
7411     focus $sstring
7412     $sstring icursor end
7413     set searchdirn -backwards
7414     if {$searchstring ne {}} {
7415         set sel [$ctext tag ranges sel]
7416         if {$sel ne {}} {
7417             set start [lindex $sel 0]
7418         } elseif {[catch {set start [$ctext index anchor]}]} {
7419             set start @0,[winfo height $ctext]
7420         }
7421         set match [$ctext search -backwards -count ml -- $searchstring $start]
7422         $ctext tag remove sel 1.0 end
7423         if {$match eq {}} {
7424             bell
7425             return
7426         }
7427         $ctext see $match
7428         set mend "$match + $ml c"
7429         $ctext tag add sel $match $mend
7430         $ctext mark unset anchor
7431     }
7432 }
7433
7434 proc searchmark {first last} {
7435     global ctext searchstring
7436
7437     set mend $first.0
7438     while {1} {
7439         set match [$ctext search -count mlen -- $searchstring $mend $last.end]
7440         if {$match eq {}} break
7441         set mend "$match + $mlen c"
7442         $ctext tag add found $match $mend
7443     }
7444 }
7445
7446 proc searchmarkvisible {doall} {
7447     global ctext smarktop smarkbot
7448
7449     set topline [lindex [split [$ctext index @0,0] .] 0]
7450     set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
7451     if {$doall || $botline < $smarktop || $topline > $smarkbot} {
7452         # no overlap with previous
7453         searchmark $topline $botline
7454         set smarktop $topline
7455         set smarkbot $botline
7456     } else {
7457         if {$topline < $smarktop} {
7458             searchmark $topline [expr {$smarktop-1}]
7459             set smarktop $topline
7460         }
7461         if {$botline > $smarkbot} {
7462             searchmark [expr {$smarkbot+1}] $botline
7463             set smarkbot $botline
7464         }
7465     }
7466 }
7467
7468 proc scrolltext {f0 f1} {
7469     global searchstring
7470
7471     .bleft.bottom.sb set $f0 $f1
7472     if {$searchstring ne {}} {
7473         searchmarkvisible 0
7474     }
7475 }
7476
7477 proc setcoords {} {
7478     global linespc charspc canvx0 canvy0
7479     global xspc1 xspc2 lthickness
7480
7481     set linespc [font metrics mainfont -linespace]
7482     set charspc [font measure mainfont "m"]
7483     set canvy0 [expr {int(3 + 0.5 * $linespc)}]
7484     set canvx0 [expr {int(3 + 0.5 * $linespc)}]
7485     set lthickness [expr {int($linespc / 9) + 1}]
7486     set xspc1(0) $linespc
7487     set xspc2 $linespc
7488 }
7489
7490 proc redisplay {} {
7491     global canv
7492     global selectedline
7493
7494     set ymax [lindex [$canv cget -scrollregion] 3]
7495     if {$ymax eq {} || $ymax == 0} return
7496     set span [$canv yview]
7497     clear_display
7498     setcanvscroll
7499     allcanvs yview moveto [lindex $span 0]
7500     drawvisible
7501     if {$selectedline ne {}} {
7502         selectline $selectedline 0
7503         allcanvs yview moveto [lindex $span 0]
7504     }
7505 }
7506
7507 proc parsefont {f n} {
7508     global fontattr
7509
7510     set fontattr($f,family) [lindex $n 0]
7511     set s [lindex $n 1]
7512     if {$s eq {} || $s == 0} {
7513         set s 10
7514     } elseif {$s < 0} {
7515         set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
7516     }
7517     set fontattr($f,size) $s
7518     set fontattr($f,weight) normal
7519     set fontattr($f,slant) roman
7520     foreach style [lrange $n 2 end] {
7521         switch -- $style {
7522             "normal" -
7523             "bold"   {set fontattr($f,weight) $style}
7524             "roman" -
7525             "italic" {set fontattr($f,slant) $style}
7526         }
7527     }
7528 }
7529
7530 proc fontflags {f {isbold 0}} {
7531     global fontattr
7532
7533     return [list -family $fontattr($f,family) -size $fontattr($f,size) \
7534                 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
7535                 -slant $fontattr($f,slant)]
7536 }
7537
7538 proc fontname {f} {
7539     global fontattr
7540
7541     set n [list $fontattr($f,family) $fontattr($f,size)]
7542     if {$fontattr($f,weight) eq "bold"} {
7543         lappend n "bold"
7544     }
7545     if {$fontattr($f,slant) eq "italic"} {
7546         lappend n "italic"
7547     }
7548     return $n
7549 }
7550
7551 proc incrfont {inc} {
7552     global mainfont textfont ctext canv cflist showrefstop
7553     global stopped entries fontattr
7554
7555     unmarkmatches
7556     set s $fontattr(mainfont,size)
7557     incr s $inc
7558     if {$s < 1} {
7559         set s 1
7560     }
7561     set fontattr(mainfont,size) $s
7562     font config mainfont -size $s
7563     font config mainfontbold -size $s
7564     set mainfont [fontname mainfont]
7565     set s $fontattr(textfont,size)
7566     incr s $inc
7567     if {$s < 1} {
7568         set s 1
7569     }
7570     set fontattr(textfont,size) $s
7571     font config textfont -size $s
7572     font config textfontbold -size $s
7573     set textfont [fontname textfont]
7574     setcoords
7575     settabs
7576     redisplay
7577 }
7578
7579 proc clearsha1 {} {
7580     global sha1entry sha1string
7581     if {[string length $sha1string] == 40} {
7582         $sha1entry delete 0 end
7583     }
7584 }
7585
7586 proc sha1change {n1 n2 op} {
7587     global sha1string currentid sha1but
7588     if {$sha1string == {}
7589         || ([info exists currentid] && $sha1string == $currentid)} {
7590         set state disabled
7591     } else {
7592         set state normal
7593     }
7594     if {[$sha1but cget -state] == $state} return
7595     if {$state == "normal"} {
7596         $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
7597     } else {
7598         $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
7599     }
7600 }
7601
7602 proc gotocommit {} {
7603     global sha1string tagids headids curview varcid
7604
7605     if {$sha1string == {}
7606         || ([info exists currentid] && $sha1string == $currentid)} return
7607     if {[info exists tagids($sha1string)]} {
7608         set id $tagids($sha1string)
7609     } elseif {[info exists headids($sha1string)]} {
7610         set id $headids($sha1string)
7611     } else {
7612         set id [string tolower $sha1string]
7613         if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7614             set matches [longid $id]
7615             if {$matches ne {}} {
7616                 if {[llength $matches] > 1} {
7617                     error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7618                     return
7619                 }
7620                 set id [lindex $matches 0]
7621             }
7622         }
7623     }
7624     if {[commitinview $id $curview]} {
7625         selectline [rowofcommit $id] 1
7626         return
7627     }
7628     if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7629         set msg [mc "SHA1 id %s is not known" $sha1string]
7630     } else {
7631         set msg [mc "Tag/Head %s is not known" $sha1string]
7632     }
7633     error_popup $msg
7634 }
7635
7636 proc lineenter {x y id} {
7637     global hoverx hovery hoverid hovertimer
7638     global commitinfo canv
7639
7640     if {![info exists commitinfo($id)] && ![getcommit $id]} return
7641     set hoverx $x
7642     set hovery $y
7643     set hoverid $id
7644     if {[info exists hovertimer]} {
7645         after cancel $hovertimer
7646     }
7647     set hovertimer [after 500 linehover]
7648     $canv delete hover
7649 }
7650
7651 proc linemotion {x y id} {
7652     global hoverx hovery hoverid hovertimer
7653
7654     if {[info exists hoverid] && $id == $hoverid} {
7655         set hoverx $x
7656         set hovery $y
7657         if {[info exists hovertimer]} {
7658             after cancel $hovertimer
7659         }
7660         set hovertimer [after 500 linehover]
7661     }
7662 }
7663
7664 proc lineleave {id} {
7665     global hoverid hovertimer canv
7666
7667     if {[info exists hoverid] && $id == $hoverid} {
7668         $canv delete hover
7669         if {[info exists hovertimer]} {
7670             after cancel $hovertimer
7671             unset hovertimer
7672         }
7673         unset hoverid
7674     }
7675 }
7676
7677 proc linehover {} {
7678     global hoverx hovery hoverid hovertimer
7679     global canv linespc lthickness
7680     global commitinfo
7681
7682     set text [lindex $commitinfo($hoverid) 0]
7683     set ymax [lindex [$canv cget -scrollregion] 3]
7684     if {$ymax == {}} return
7685     set yfrac [lindex [$canv yview] 0]
7686     set x [expr {$hoverx + 2 * $linespc}]
7687     set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7688     set x0 [expr {$x - 2 * $lthickness}]
7689     set y0 [expr {$y - 2 * $lthickness}]
7690     set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7691     set y1 [expr {$y + $linespc + 2 * $lthickness}]
7692     set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7693                -fill \#ffff80 -outline black -width 1 -tags hover]
7694     $canv raise $t
7695     set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7696                -font mainfont]
7697     $canv raise $t
7698 }
7699
7700 proc clickisonarrow {id y} {
7701     global lthickness
7702
7703     set ranges [rowranges $id]
7704     set thresh [expr {2 * $lthickness + 6}]
7705     set n [expr {[llength $ranges] - 1}]
7706     for {set i 1} {$i < $n} {incr i} {
7707         set row [lindex $ranges $i]
7708         if {abs([yc $row] - $y) < $thresh} {
7709             return $i
7710         }
7711     }
7712     return {}
7713 }
7714
7715 proc arrowjump {id n y} {
7716     global canv
7717
7718     # 1 <-> 2, 3 <-> 4, etc...
7719     set n [expr {(($n - 1) ^ 1) + 1}]
7720     set row [lindex [rowranges $id] $n]
7721     set yt [yc $row]
7722     set ymax [lindex [$canv cget -scrollregion] 3]
7723     if {$ymax eq {} || $ymax <= 0} return
7724     set view [$canv yview]
7725     set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7726     set yfrac [expr {$yt / $ymax - $yspan / 2}]
7727     if {$yfrac < 0} {
7728         set yfrac 0
7729     }
7730     allcanvs yview moveto $yfrac
7731 }
7732
7733 proc lineclick {x y id isnew} {
7734     global ctext commitinfo children canv thickerline curview
7735
7736     if {![info exists commitinfo($id)] && ![getcommit $id]} return
7737     unmarkmatches
7738     unselectline
7739     normalline
7740     $canv delete hover
7741     # draw this line thicker than normal
7742     set thickerline $id
7743     drawlines $id
7744     if {$isnew} {
7745         set ymax [lindex [$canv cget -scrollregion] 3]
7746         if {$ymax eq {}} return
7747         set yfrac [lindex [$canv yview] 0]
7748         set y [expr {$y + $yfrac * $ymax}]
7749     }
7750     set dirn [clickisonarrow $id $y]
7751     if {$dirn ne {}} {
7752         arrowjump $id $dirn $y
7753         return
7754     }
7755
7756     if {$isnew} {
7757         addtohistory [list lineclick $x $y $id 0]
7758     }
7759     # fill the details pane with info about this line
7760     $ctext conf -state normal
7761     clear_ctext
7762     settabs 0
7763     $ctext insert end "[mc "Parent"]:\t"
7764     $ctext insert end $id link0
7765     setlink $id link0
7766     set info $commitinfo($id)
7767     $ctext insert end "\n\t[lindex $info 0]\n"
7768     $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7769     set date [formatdate [lindex $info 2]]
7770     $ctext insert end "\t[mc "Date"]:\t$date\n"
7771     set kids $children($curview,$id)
7772     if {$kids ne {}} {
7773         $ctext insert end "\n[mc "Children"]:"
7774         set i 0
7775         foreach child $kids {
7776             incr i
7777             if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7778             set info $commitinfo($child)
7779             $ctext insert end "\n\t"
7780             $ctext insert end $child link$i
7781             setlink $child link$i
7782             $ctext insert end "\n\t[lindex $info 0]"
7783             $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7784             set date [formatdate [lindex $info 2]]
7785             $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7786         }
7787     }
7788     $ctext conf -state disabled
7789     init_flist {}
7790 }
7791
7792 proc normalline {} {
7793     global thickerline
7794     if {[info exists thickerline]} {
7795         set id $thickerline
7796         unset thickerline
7797         drawlines $id
7798     }
7799 }
7800
7801 proc selbyid {id} {
7802     global curview
7803     if {[commitinview $id $curview]} {
7804         selectline [rowofcommit $id] 1
7805     }
7806 }
7807
7808 proc mstime {} {
7809     global startmstime
7810     if {![info exists startmstime]} {
7811         set startmstime [clock clicks -milliseconds]
7812     }
7813     return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7814 }
7815
7816 proc rowmenu {x y id} {
7817     global rowctxmenu selectedline rowmenuid curview
7818     global nullid nullid2 fakerowmenu mainhead
7819
7820     stopfinding
7821     set rowmenuid $id
7822     if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7823         set state disabled
7824     } else {
7825         set state normal
7826     }
7827     if {$id ne $nullid && $id ne $nullid2} {
7828         set menu $rowctxmenu
7829         if {$mainhead ne {}} {
7830             $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7831         } else {
7832             $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7833         }
7834     } else {
7835         set menu $fakerowmenu
7836     }
7837     $menu entryconfigure [mca "Diff this -> selected"] -state $state
7838     $menu entryconfigure [mca "Diff selected -> this"] -state $state
7839     $menu entryconfigure [mca "Make patch"] -state $state
7840     tk_popup $menu $x $y
7841 }
7842
7843 proc diffvssel {dirn} {
7844     global rowmenuid selectedline
7845
7846     if {$selectedline eq {}} return
7847     if {$dirn} {
7848         set oldid [commitonrow $selectedline]
7849         set newid $rowmenuid
7850     } else {
7851         set oldid $rowmenuid
7852         set newid [commitonrow $selectedline]
7853     }
7854     addtohistory [list doseldiff $oldid $newid]
7855     doseldiff $oldid $newid
7856 }
7857
7858 proc doseldiff {oldid newid} {
7859     global ctext
7860     global commitinfo
7861
7862     $ctext conf -state normal
7863     clear_ctext
7864     init_flist [mc "Top"]
7865     $ctext insert end "[mc "From"] "
7866     $ctext insert end $oldid link0
7867     setlink $oldid link0
7868     $ctext insert end "\n     "
7869     $ctext insert end [lindex $commitinfo($oldid) 0]
7870     $ctext insert end "\n\n[mc "To"]   "
7871     $ctext insert end $newid link1
7872     setlink $newid link1
7873     $ctext insert end "\n     "
7874     $ctext insert end [lindex $commitinfo($newid) 0]
7875     $ctext insert end "\n"
7876     $ctext conf -state disabled
7877     $ctext tag remove found 1.0 end
7878     startdiff [list $oldid $newid]
7879 }
7880
7881 proc mkpatch {} {
7882     global rowmenuid currentid commitinfo patchtop patchnum
7883
7884     if {![info exists currentid]} return
7885     set oldid $currentid
7886     set oldhead [lindex $commitinfo($oldid) 0]
7887     set newid $rowmenuid
7888     set newhead [lindex $commitinfo($newid) 0]
7889     set top .patch
7890     set patchtop $top
7891     catch {destroy $top}
7892     toplevel $top
7893     wm transient $top .
7894     label $top.title -text [mc "Generate patch"]
7895     grid $top.title - -pady 10
7896     label $top.from -text [mc "From:"]
7897     entry $top.fromsha1 -width 40 -relief flat
7898     $top.fromsha1 insert 0 $oldid
7899     $top.fromsha1 conf -state readonly
7900     grid $top.from $top.fromsha1 -sticky w
7901     entry $top.fromhead -width 60 -relief flat
7902     $top.fromhead insert 0 $oldhead
7903     $top.fromhead conf -state readonly
7904     grid x $top.fromhead -sticky w
7905     label $top.to -text [mc "To:"]
7906     entry $top.tosha1 -width 40 -relief flat
7907     $top.tosha1 insert 0 $newid
7908     $top.tosha1 conf -state readonly
7909     grid $top.to $top.tosha1 -sticky w
7910     entry $top.tohead -width 60 -relief flat
7911     $top.tohead insert 0 $newhead
7912     $top.tohead conf -state readonly
7913     grid x $top.tohead -sticky w
7914     button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7915     grid $top.rev x -pady 10
7916     label $top.flab -text [mc "Output file:"]
7917     entry $top.fname -width 60
7918     $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7919     incr patchnum
7920     grid $top.flab $top.fname -sticky w
7921     frame $top.buts
7922     button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7923     button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7924     bind $top <Key-Return> mkpatchgo
7925     bind $top <Key-Escape> mkpatchcan
7926     grid $top.buts.gen $top.buts.can
7927     grid columnconfigure $top.buts 0 -weight 1 -uniform a
7928     grid columnconfigure $top.buts 1 -weight 1 -uniform a
7929     grid $top.buts - -pady 10 -sticky ew
7930     focus $top.fname
7931 }
7932
7933 proc mkpatchrev {} {
7934     global patchtop
7935
7936     set oldid [$patchtop.fromsha1 get]
7937     set oldhead [$patchtop.fromhead get]
7938     set newid [$patchtop.tosha1 get]
7939     set newhead [$patchtop.tohead get]
7940     foreach e [list fromsha1 fromhead tosha1 tohead] \
7941             v [list $newid $newhead $oldid $oldhead] {
7942         $patchtop.$e conf -state normal
7943         $patchtop.$e delete 0 end
7944         $patchtop.$e insert 0 $v
7945         $patchtop.$e conf -state readonly
7946     }
7947 }
7948
7949 proc mkpatchgo {} {
7950     global patchtop nullid nullid2
7951
7952     set oldid [$patchtop.fromsha1 get]
7953     set newid [$patchtop.tosha1 get]
7954     set fname [$patchtop.fname get]
7955     set cmd [diffcmd [list $oldid $newid] -p]
7956     # trim off the initial "|"
7957     set cmd [lrange $cmd 1 end]
7958     lappend cmd >$fname &
7959     if {[catch {eval exec $cmd} err]} {
7960         error_popup "[mc "Error creating patch:"] $err" $patchtop
7961     }
7962     catch {destroy $patchtop}
7963     unset patchtop
7964 }
7965
7966 proc mkpatchcan {} {
7967     global patchtop
7968
7969     catch {destroy $patchtop}
7970     unset patchtop
7971 }
7972
7973 proc mktag {} {
7974     global rowmenuid mktagtop commitinfo
7975
7976     set top .maketag
7977     set mktagtop $top
7978     catch {destroy $top}
7979     toplevel $top
7980     wm transient $top .
7981     label $top.title -text [mc "Create tag"]
7982     grid $top.title - -pady 10
7983     label $top.id -text [mc "ID:"]
7984     entry $top.sha1 -width 40 -relief flat
7985     $top.sha1 insert 0 $rowmenuid
7986     $top.sha1 conf -state readonly
7987     grid $top.id $top.sha1 -sticky w
7988     entry $top.head -width 60 -relief flat
7989     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7990     $top.head conf -state readonly
7991     grid x $top.head -sticky w
7992     label $top.tlab -text [mc "Tag name:"]
7993     entry $top.tag -width 60
7994     grid $top.tlab $top.tag -sticky w
7995     frame $top.buts
7996     button $top.buts.gen -text [mc "Create"] -command mktaggo
7997     button $top.buts.can -text [mc "Cancel"] -command mktagcan
7998     bind $top <Key-Return> mktaggo
7999     bind $top <Key-Escape> mktagcan
8000     grid $top.buts.gen $top.buts.can
8001     grid columnconfigure $top.buts 0 -weight 1 -uniform a
8002     grid columnconfigure $top.buts 1 -weight 1 -uniform a
8003     grid $top.buts - -pady 10 -sticky ew
8004     focus $top.tag
8005 }
8006
8007 proc domktag {} {
8008     global mktagtop env tagids idtags
8009
8010     set id [$mktagtop.sha1 get]
8011     set tag [$mktagtop.tag get]
8012     if {$tag == {}} {
8013         error_popup [mc "No tag name specified"] $mktagtop
8014         return 0
8015     }
8016     if {[info exists tagids($tag)]} {
8017         error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
8018         return 0
8019     }
8020     if {[catch {
8021         exec git tag $tag $id
8022     } err]} {
8023         error_popup "[mc "Error creating tag:"] $err" $mktagtop
8024         return 0
8025     }
8026
8027     set tagids($tag) $id
8028     lappend idtags($id) $tag
8029     redrawtags $id
8030     addedtag $id
8031     dispneartags 0
8032     run refill_reflist
8033     return 1
8034 }
8035
8036 proc redrawtags {id} {
8037     global canv linehtag idpos currentid curview cmitlisted
8038     global canvxmax iddrawn circleitem mainheadid circlecolors
8039
8040     if {![commitinview $id $curview]} return
8041     if {![info exists iddrawn($id)]} return
8042     set row [rowofcommit $id]
8043     if {$id eq $mainheadid} {
8044         set ofill yellow
8045     } else {
8046         set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
8047     }
8048     $canv itemconf $circleitem($row) -fill $ofill
8049     $canv delete tag.$id
8050     set xt [eval drawtags $id $idpos($id)]
8051     $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
8052     set text [$canv itemcget $linehtag($row) -text]
8053     set font [$canv itemcget $linehtag($row) -font]
8054     set xr [expr {$xt + [font measure $font $text]}]
8055     if {$xr > $canvxmax} {
8056         set canvxmax $xr
8057         setcanvscroll
8058     }
8059     if {[info exists currentid] && $currentid == $id} {
8060         make_secsel $row
8061     }
8062 }
8063
8064 proc mktagcan {} {
8065     global mktagtop
8066
8067     catch {destroy $mktagtop}
8068     unset mktagtop
8069 }
8070
8071 proc mktaggo {} {
8072     if {![domktag]} return
8073     mktagcan
8074 }
8075
8076 proc writecommit {} {
8077     global rowmenuid wrcomtop commitinfo wrcomcmd
8078
8079     set top .writecommit
8080     set wrcomtop $top
8081     catch {destroy $top}
8082     toplevel $top
8083     wm transient $top .
8084     label $top.title -text [mc "Write commit to file"]
8085     grid $top.title - -pady 10
8086     label $top.id -text [mc "ID:"]
8087     entry $top.sha1 -width 40 -relief flat
8088     $top.sha1 insert 0 $rowmenuid
8089     $top.sha1 conf -state readonly
8090     grid $top.id $top.sha1 -sticky w
8091     entry $top.head -width 60 -relief flat
8092     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8093     $top.head conf -state readonly
8094     grid x $top.head -sticky w
8095     label $top.clab -text [mc "Command:"]
8096     entry $top.cmd -width 60 -textvariable wrcomcmd
8097     grid $top.clab $top.cmd -sticky w -pady 10
8098     label $top.flab -text [mc "Output file:"]
8099     entry $top.fname -width 60
8100     $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
8101     grid $top.flab $top.fname -sticky w
8102     frame $top.buts
8103     button $top.buts.gen -text [mc "Write"] -command wrcomgo
8104     button $top.buts.can -text [mc "Cancel"] -command wrcomcan
8105     bind $top <Key-Return> wrcomgo
8106     bind $top <Key-Escape> wrcomcan
8107     grid $top.buts.gen $top.buts.can
8108     grid columnconfigure $top.buts 0 -weight 1 -uniform a
8109     grid columnconfigure $top.buts 1 -weight 1 -uniform a
8110     grid $top.buts - -pady 10 -sticky ew
8111     focus $top.fname
8112 }
8113
8114 proc wrcomgo {} {
8115     global wrcomtop
8116
8117     set id [$wrcomtop.sha1 get]
8118     set cmd "echo $id | [$wrcomtop.cmd get]"
8119     set fname [$wrcomtop.fname get]
8120     if {[catch {exec sh -c $cmd >$fname &} err]} {
8121         error_popup "[mc "Error writing commit:"] $err" $wrcomtop
8122     }
8123     catch {destroy $wrcomtop}
8124     unset wrcomtop
8125 }
8126
8127 proc wrcomcan {} {
8128     global wrcomtop
8129
8130     catch {destroy $wrcomtop}
8131     unset wrcomtop
8132 }
8133
8134 proc mkbranch {} {
8135     global rowmenuid mkbrtop
8136
8137     set top .makebranch
8138     catch {destroy $top}
8139     toplevel $top
8140     wm transient $top .
8141     label $top.title -text [mc "Create new branch"]
8142     grid $top.title - -pady 10
8143     label $top.id -text [mc "ID:"]
8144     entry $top.sha1 -width 40 -relief flat
8145     $top.sha1 insert 0 $rowmenuid
8146     $top.sha1 conf -state readonly
8147     grid $top.id $top.sha1 -sticky w
8148     label $top.nlab -text [mc "Name:"]
8149     entry $top.name -width 40
8150     bind $top.name <Key-Return> "[list mkbrgo $top]"
8151     grid $top.nlab $top.name -sticky w
8152     frame $top.buts
8153     button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
8154     button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
8155     bind $top <Key-Return> [list mkbrgo $top]
8156     bind $top <Key-Escape> "catch {destroy $top}"
8157     grid $top.buts.go $top.buts.can
8158     grid columnconfigure $top.buts 0 -weight 1 -uniform a
8159     grid columnconfigure $top.buts 1 -weight 1 -uniform a
8160     grid $top.buts - -pady 10 -sticky ew
8161     focus $top.name
8162 }
8163
8164 proc mkbrgo {top} {
8165     global headids idheads
8166
8167     set name [$top.name get]
8168     set id [$top.sha1 get]
8169     set cmdargs {}
8170     set old_id {}
8171     if {$name eq {}} {
8172         error_popup [mc "Please specify a name for the new branch"] $top
8173         return
8174     }
8175     if {[info exists headids($name)]} {
8176         if {![confirm_popup [mc \
8177                 "Branch '%s' already exists. Overwrite?" $name] $top]} {
8178             return
8179         }
8180         set old_id $headids($name)
8181         lappend cmdargs -f
8182     }
8183     catch {destroy $top}
8184     lappend cmdargs $name $id
8185     nowbusy newbranch
8186     update
8187     if {[catch {
8188         eval exec git branch $cmdargs
8189     } err]} {
8190         notbusy newbranch
8191         error_popup $err
8192     } else {
8193         notbusy newbranch
8194         if {$old_id ne {}} {
8195             movehead $id $name
8196             movedhead $id $name
8197             redrawtags $old_id
8198             redrawtags $id
8199         } else {
8200             set headids($name) $id
8201             lappend idheads($id) $name
8202             addedhead $id $name
8203             redrawtags $id
8204         }
8205         dispneartags 0
8206         run refill_reflist
8207     }
8208 }
8209
8210 proc exec_citool {tool_args {baseid {}}} {
8211     global commitinfo env
8212
8213     set save_env [array get env GIT_AUTHOR_*]
8214
8215     if {$baseid ne {}} {
8216         if {![info exists commitinfo($baseid)]} {
8217             getcommit $baseid
8218         }
8219         set author [lindex $commitinfo($baseid) 1]
8220         set date [lindex $commitinfo($baseid) 2]
8221         if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
8222                     $author author name email]
8223             && $date ne {}} {
8224             set env(GIT_AUTHOR_NAME) $name
8225             set env(GIT_AUTHOR_EMAIL) $email
8226             set env(GIT_AUTHOR_DATE) $date
8227         }
8228     }
8229
8230     eval exec git citool $tool_args &
8231
8232     array unset env GIT_AUTHOR_*
8233     array set env $save_env
8234 }
8235
8236 proc cherrypick {} {
8237     global rowmenuid curview
8238     global mainhead mainheadid
8239
8240     set oldhead [exec git rev-parse HEAD]
8241     set dheads [descheads $rowmenuid]
8242     if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
8243         set ok [confirm_popup [mc "Commit %s is already\
8244                 included in branch %s -- really re-apply it?" \
8245                                    [string range $rowmenuid 0 7] $mainhead]]
8246         if {!$ok} return
8247     }
8248     nowbusy cherrypick [mc "Cherry-picking"]
8249     update
8250     # Unfortunately git-cherry-pick writes stuff to stderr even when
8251     # no error occurs, and exec takes that as an indication of error...
8252     if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
8253         notbusy cherrypick
8254         if {[regexp -line \
8255                  {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
8256                  $err msg fname]} {
8257             error_popup [mc "Cherry-pick failed because of local changes\
8258                         to file '%s'.\nPlease commit, reset or stash\
8259                         your changes and try again." $fname]
8260         } elseif {[regexp -line \
8261                        {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \
8262                        $err]} {
8263             if {[confirm_popup [mc "Cherry-pick failed because of merge\
8264                         conflict.\nDo you wish to run git citool to\
8265                         resolve it?"]]} {
8266                 # Force citool to read MERGE_MSG
8267                 file delete [file join [gitdir] "GITGUI_MSG"]
8268                 exec_citool {} $rowmenuid
8269             }
8270         } else {
8271             error_popup $err
8272         }
8273         run updatecommits
8274         return
8275     }
8276     set newhead [exec git rev-parse HEAD]
8277     if {$newhead eq $oldhead} {
8278         notbusy cherrypick
8279         error_popup [mc "No changes committed"]
8280         return
8281     }
8282     addnewchild $newhead $oldhead
8283     if {[commitinview $oldhead $curview]} {
8284         insertrow $newhead $oldhead $curview
8285         if {$mainhead ne {}} {
8286             movehead $newhead $mainhead
8287             movedhead $newhead $mainhead
8288         }
8289         set mainheadid $newhead
8290         redrawtags $oldhead
8291         redrawtags $newhead
8292         selbyid $newhead
8293     }
8294     notbusy cherrypick
8295 }
8296
8297 proc resethead {} {
8298     global mainhead rowmenuid confirm_ok resettype
8299
8300     set confirm_ok 0
8301     set w ".confirmreset"
8302     toplevel $w
8303     wm transient $w .
8304     wm title $w [mc "Confirm reset"]
8305     message $w.m -text \
8306         [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
8307         -justify center -aspect 1000
8308     pack $w.m -side top -fill x -padx 20 -pady 20
8309     frame $w.f -relief sunken -border 2
8310     message $w.f.rt -text [mc "Reset type:"] -aspect 1000
8311     grid $w.f.rt -sticky w
8312     set resettype mixed
8313     radiobutton $w.f.soft -value soft -variable resettype -justify left \
8314         -text [mc "Soft: Leave working tree and index untouched"]
8315     grid $w.f.soft -sticky w
8316     radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
8317         -text [mc "Mixed: Leave working tree untouched, reset index"]
8318     grid $w.f.mixed -sticky w
8319     radiobutton $w.f.hard -value hard -variable resettype -justify left \
8320         -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
8321     grid $w.f.hard -sticky w
8322     pack $w.f -side top -fill x
8323     button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
8324     pack $w.ok -side left -fill x -padx 20 -pady 20
8325     button $w.cancel -text [mc Cancel] -command "destroy $w"
8326     bind $w <Key-Escape> [list destroy $w]
8327     pack $w.cancel -side right -fill x -padx 20 -pady 20
8328     bind $w <Visibility> "grab $w; focus $w"
8329     tkwait window $w
8330     if {!$confirm_ok} return
8331     if {[catch {set fd [open \
8332             [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
8333         error_popup $err
8334     } else {
8335         dohidelocalchanges
8336         filerun $fd [list readresetstat $fd]
8337         nowbusy reset [mc "Resetting"]
8338         selbyid $rowmenuid
8339     }
8340 }
8341
8342 proc readresetstat {fd} {
8343     global mainhead mainheadid showlocalchanges rprogcoord
8344
8345     if {[gets $fd line] >= 0} {
8346         if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8347             set rprogcoord [expr {1.0 * $m / $n}]
8348             adjustprogress
8349         }
8350         return 1
8351     }
8352     set rprogcoord 0
8353     adjustprogress
8354     notbusy reset
8355     if {[catch {close $fd} err]} {
8356         error_popup $err
8357     }
8358     set oldhead $mainheadid
8359     set newhead [exec git rev-parse HEAD]
8360     if {$newhead ne $oldhead} {
8361         movehead $newhead $mainhead
8362         movedhead $newhead $mainhead
8363         set mainheadid $newhead
8364         redrawtags $oldhead
8365         redrawtags $newhead
8366     }
8367     if {$showlocalchanges} {
8368         doshowlocalchanges
8369     }
8370     return 0
8371 }
8372
8373 # context menu for a head
8374 proc headmenu {x y id head} {
8375     global headmenuid headmenuhead headctxmenu mainhead
8376
8377     stopfinding
8378     set headmenuid $id
8379     set headmenuhead $head
8380     set state normal
8381     if {$head eq $mainhead} {
8382         set state disabled
8383     }
8384     $headctxmenu entryconfigure 0 -state $state
8385     $headctxmenu entryconfigure 1 -state $state
8386     tk_popup $headctxmenu $x $y
8387 }
8388
8389 proc cobranch {} {
8390     global headmenuid headmenuhead headids
8391     global showlocalchanges mainheadid
8392
8393     # check the tree is clean first??
8394     nowbusy checkout [mc "Checking out"]
8395     update
8396     dohidelocalchanges
8397     if {[catch {
8398         set fd [open [list | git checkout $headmenuhead 2>@1] r]
8399     } err]} {
8400         notbusy checkout
8401         error_popup $err
8402         if {$showlocalchanges} {
8403             dodiffindex
8404         }
8405     } else {
8406         filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
8407     }
8408 }
8409
8410 proc readcheckoutstat {fd newhead newheadid} {
8411     global mainhead mainheadid headids showlocalchanges progresscoords
8412
8413     if {[gets $fd line] >= 0} {
8414         if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8415             set progresscoords [list 0 [expr {1.0 * $m / $n}]]
8416             adjustprogress
8417         }
8418         return 1
8419     }
8420     set progresscoords {0 0}
8421     adjustprogress
8422     notbusy checkout
8423     if {[catch {close $fd} err]} {
8424         error_popup $err
8425     }
8426     set oldmainid $mainheadid
8427     set mainhead $newhead
8428     set mainheadid $newheadid
8429     redrawtags $oldmainid
8430     redrawtags $newheadid
8431     selbyid $newheadid
8432     if {$showlocalchanges} {
8433         dodiffindex
8434     }
8435 }
8436
8437 proc rmbranch {} {
8438     global headmenuid headmenuhead mainhead
8439     global idheads
8440
8441     set head $headmenuhead
8442     set id $headmenuid
8443     # this check shouldn't be needed any more...
8444     if {$head eq $mainhead} {
8445         error_popup [mc "Cannot delete the currently checked-out branch"]
8446         return
8447     }
8448     set dheads [descheads $id]
8449     if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
8450         # the stuff on this branch isn't on any other branch
8451         if {![confirm_popup [mc "The commits on branch %s aren't on any other\
8452                         branch.\nReally delete branch %s?" $head $head]]} return
8453     }
8454     nowbusy rmbranch
8455     update
8456     if {[catch {exec git branch -D $head} err]} {
8457         notbusy rmbranch
8458         error_popup $err
8459         return
8460     }
8461     removehead $id $head
8462     removedhead $id $head
8463     redrawtags $id
8464     notbusy rmbranch
8465     dispneartags 0
8466     run refill_reflist
8467 }
8468
8469 # Display a list of tags and heads
8470 proc showrefs {} {
8471     global showrefstop bgcolor fgcolor selectbgcolor
8472     global bglist fglist reflistfilter reflist maincursor
8473
8474     set top .showrefs
8475     set showrefstop $top
8476     if {[winfo exists $top]} {
8477         raise $top
8478         refill_reflist
8479         return
8480     }
8481     toplevel $top
8482     wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
8483     wm transient $top .
8484     text $top.list -background $bgcolor -foreground $fgcolor \
8485         -selectbackground $selectbgcolor -font mainfont \
8486         -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
8487         -width 30 -height 20 -cursor $maincursor \
8488         -spacing1 1 -spacing3 1 -state disabled
8489     $top.list tag configure highlight -background $selectbgcolor
8490     lappend bglist $top.list
8491     lappend fglist $top.list
8492     scrollbar $top.ysb -command "$top.list yview" -orient vertical
8493     scrollbar $top.xsb -command "$top.list xview" -orient horizontal
8494     grid $top.list $top.ysb -sticky nsew
8495     grid $top.xsb x -sticky ew
8496     frame $top.f
8497     label $top.f.l -text "[mc "Filter"]: "
8498     entry $top.f.e -width 20 -textvariable reflistfilter
8499     set reflistfilter "*"
8500     trace add variable reflistfilter write reflistfilter_change
8501     pack $top.f.e -side right -fill x -expand 1
8502     pack $top.f.l -side left
8503     grid $top.f - -sticky ew -pady 2
8504     button $top.close -command [list destroy $top] -text [mc "Close"]
8505     bind $top <Key-Escape> [list destroy $top]
8506     grid $top.close -
8507     grid columnconfigure $top 0 -weight 1
8508     grid rowconfigure $top 0 -weight 1
8509     bind $top.list <1> {break}
8510     bind $top.list <B1-Motion> {break}
8511     bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
8512     set reflist {}
8513     refill_reflist
8514 }
8515
8516 proc sel_reflist {w x y} {
8517     global showrefstop reflist headids tagids otherrefids
8518
8519     if {![winfo exists $showrefstop]} return
8520     set l [lindex [split [$w index "@$x,$y"] "."] 0]
8521     set ref [lindex $reflist [expr {$l-1}]]
8522     set n [lindex $ref 0]
8523     switch -- [lindex $ref 1] {
8524         "H" {selbyid $headids($n)}
8525         "T" {selbyid $tagids($n)}
8526         "o" {selbyid $otherrefids($n)}
8527     }
8528     $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
8529 }
8530
8531 proc unsel_reflist {} {
8532     global showrefstop
8533
8534     if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8535     $showrefstop.list tag remove highlight 0.0 end
8536 }
8537
8538 proc reflistfilter_change {n1 n2 op} {
8539     global reflistfilter
8540
8541     after cancel refill_reflist
8542     after 200 refill_reflist
8543 }
8544
8545 proc refill_reflist {} {
8546     global reflist reflistfilter showrefstop headids tagids otherrefids
8547     global curview
8548
8549     if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8550     set refs {}
8551     foreach n [array names headids] {
8552         if {[string match $reflistfilter $n]} {
8553             if {[commitinview $headids($n) $curview]} {
8554                 lappend refs [list $n H]
8555             } else {
8556                 interestedin $headids($n) {run refill_reflist}
8557             }
8558         }
8559     }
8560     foreach n [array names tagids] {
8561         if {[string match $reflistfilter $n]} {
8562             if {[commitinview $tagids($n) $curview]} {
8563                 lappend refs [list $n T]
8564             } else {
8565                 interestedin $tagids($n) {run refill_reflist}
8566             }
8567         }
8568     }
8569     foreach n [array names otherrefids] {
8570         if {[string match $reflistfilter $n]} {
8571             if {[commitinview $otherrefids($n) $curview]} {
8572                 lappend refs [list $n o]
8573             } else {
8574                 interestedin $otherrefids($n) {run refill_reflist}
8575             }
8576         }
8577     }
8578     set refs [lsort -index 0 $refs]
8579     if {$refs eq $reflist} return
8580
8581     # Update the contents of $showrefstop.list according to the
8582     # differences between $reflist (old) and $refs (new)
8583     $showrefstop.list conf -state normal
8584     $showrefstop.list insert end "\n"
8585     set i 0
8586     set j 0
8587     while {$i < [llength $reflist] || $j < [llength $refs]} {
8588         if {$i < [llength $reflist]} {
8589             if {$j < [llength $refs]} {
8590                 set cmp [string compare [lindex $reflist $i 0] \
8591                              [lindex $refs $j 0]]
8592                 if {$cmp == 0} {
8593                     set cmp [string compare [lindex $reflist $i 1] \
8594                                  [lindex $refs $j 1]]
8595                 }
8596             } else {
8597                 set cmp -1
8598             }
8599         } else {
8600             set cmp 1
8601         }
8602         switch -- $cmp {
8603             -1 {
8604                 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
8605                 incr i
8606             }
8607             0 {
8608                 incr i
8609                 incr j
8610             }
8611             1 {
8612                 set l [expr {$j + 1}]
8613                 $showrefstop.list image create $l.0 -align baseline \
8614                     -image reficon-[lindex $refs $j 1] -padx 2
8615                 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
8616                 incr j
8617             }
8618         }
8619     }
8620     set reflist $refs
8621     # delete last newline
8622     $showrefstop.list delete end-2c end-1c
8623     $showrefstop.list conf -state disabled
8624 }
8625
8626 # Stuff for finding nearby tags
8627 proc getallcommits {} {
8628     global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
8629     global idheads idtags idotherrefs allparents tagobjid
8630
8631     if {![info exists allcommits]} {
8632         set nextarc 0
8633         set allcommits 0
8634         set seeds {}
8635         set allcwait 0
8636         set cachedarcs 0
8637         set allccache [file join [gitdir] "gitk.cache"]
8638         if {![catch {
8639             set f [open $allccache r]
8640             set allcwait 1
8641             getcache $f
8642         }]} return
8643     }
8644
8645     if {$allcwait} {
8646         return
8647     }
8648     set cmd [list | git rev-list --parents]
8649     set allcupdate [expr {$seeds ne {}}]
8650     if {!$allcupdate} {
8651         set ids "--all"
8652     } else {
8653         set refs [concat [array names idheads] [array names idtags] \
8654                       [array names idotherrefs]]
8655         set ids {}
8656         set tagobjs {}
8657         foreach name [array names tagobjid] {
8658             lappend tagobjs $tagobjid($name)
8659         }
8660         foreach id [lsort -unique $refs] {
8661             if {![info exists allparents($id)] &&
8662                 [lsearch -exact $tagobjs $id] < 0} {
8663                 lappend ids $id
8664             }
8665         }
8666         if {$ids ne {}} {
8667             foreach id $seeds {
8668                 lappend ids "^$id"
8669             }
8670         }
8671     }
8672     if {$ids ne {}} {
8673         set fd [open [concat $cmd $ids] r]
8674         fconfigure $fd -blocking 0
8675         incr allcommits
8676         nowbusy allcommits
8677         filerun $fd [list getallclines $fd]
8678     } else {
8679         dispneartags 0
8680     }
8681 }
8682
8683 # Since most commits have 1 parent and 1 child, we group strings of
8684 # such commits into "arcs" joining branch/merge points (BMPs), which
8685 # are commits that either don't have 1 parent or don't have 1 child.
8686 #
8687 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8688 # arcout(id) - outgoing arcs for BMP
8689 # arcids(a) - list of IDs on arc including end but not start
8690 # arcstart(a) - BMP ID at start of arc
8691 # arcend(a) - BMP ID at end of arc
8692 # growing(a) - arc a is still growing
8693 # arctags(a) - IDs out of arcids (excluding end) that have tags
8694 # archeads(a) - IDs out of arcids (excluding end) that have heads
8695 # The start of an arc is at the descendent end, so "incoming" means
8696 # coming from descendents, and "outgoing" means going towards ancestors.
8697
8698 proc getallclines {fd} {
8699     global allparents allchildren idtags idheads nextarc
8700     global arcnos arcids arctags arcout arcend arcstart archeads growing
8701     global seeds allcommits cachedarcs allcupdate
8702     
8703     set nid 0
8704     while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8705         set id [lindex $line 0]
8706         if {[info exists allparents($id)]} {
8707             # seen it already
8708             continue
8709         }
8710         set cachedarcs 0
8711         set olds [lrange $line 1 end]
8712         set allparents($id) $olds
8713         if {![info exists allchildren($id)]} {
8714             set allchildren($id) {}
8715             set arcnos($id) {}
8716             lappend seeds $id
8717         } else {
8718             set a $arcnos($id)
8719             if {[llength $olds] == 1 && [llength $a] == 1} {
8720                 lappend arcids($a) $id
8721                 if {[info exists idtags($id)]} {
8722                     lappend arctags($a) $id
8723                 }
8724                 if {[info exists idheads($id)]} {
8725                     lappend archeads($a) $id
8726                 }
8727                 if {[info exists allparents($olds)]} {
8728                     # seen parent already
8729                     if {![info exists arcout($olds)]} {
8730                         splitarc $olds
8731                     }
8732                     lappend arcids($a) $olds
8733                     set arcend($a) $olds
8734                     unset growing($a)
8735                 }
8736                 lappend allchildren($olds) $id
8737                 lappend arcnos($olds) $a
8738                 continue
8739             }
8740         }
8741         foreach a $arcnos($id) {
8742             lappend arcids($a) $id
8743             set arcend($a) $id
8744             unset growing($a)
8745         }
8746
8747         set ao {}
8748         foreach p $olds {
8749             lappend allchildren($p) $id
8750             set a [incr nextarc]
8751             set arcstart($a) $id
8752             set archeads($a) {}
8753             set arctags($a) {}
8754             set archeads($a) {}
8755             set arcids($a) {}
8756             lappend ao $a
8757             set growing($a) 1
8758             if {[info exists allparents($p)]} {
8759                 # seen it already, may need to make a new branch
8760                 if {![info exists arcout($p)]} {
8761                     splitarc $p
8762                 }
8763                 lappend arcids($a) $p
8764                 set arcend($a) $p
8765                 unset growing($a)
8766             }
8767             lappend arcnos($p) $a
8768         }
8769         set arcout($id) $ao
8770     }
8771     if {$nid > 0} {
8772         global cached_dheads cached_dtags cached_atags
8773         catch {unset cached_dheads}
8774         catch {unset cached_dtags}
8775         catch {unset cached_atags}
8776     }
8777     if {![eof $fd]} {
8778         return [expr {$nid >= 1000? 2: 1}]
8779     }
8780     set cacheok 1
8781     if {[catch {
8782         fconfigure $fd -blocking 1
8783         close $fd
8784     } err]} {
8785         # got an error reading the list of commits
8786         # if we were updating, try rereading the whole thing again
8787         if {$allcupdate} {
8788             incr allcommits -1
8789             dropcache $err
8790             return
8791         }
8792         error_popup "[mc "Error reading commit topology information;\
8793                 branch and preceding/following tag information\
8794                 will be incomplete."]\n($err)"
8795         set cacheok 0
8796     }
8797     if {[incr allcommits -1] == 0} {
8798         notbusy allcommits
8799         if {$cacheok} {
8800             run savecache
8801         }
8802     }
8803     dispneartags 0
8804     return 0
8805 }
8806
8807 proc recalcarc {a} {
8808     global arctags archeads arcids idtags idheads
8809
8810     set at {}
8811     set ah {}
8812     foreach id [lrange $arcids($a) 0 end-1] {
8813         if {[info exists idtags($id)]} {
8814             lappend at $id
8815         }
8816         if {[info exists idheads($id)]} {
8817             lappend ah $id
8818         }
8819     }
8820     set arctags($a) $at
8821     set archeads($a) $ah
8822 }
8823
8824 proc splitarc {p} {
8825     global arcnos arcids nextarc arctags archeads idtags idheads
8826     global arcstart arcend arcout allparents growing
8827
8828     set a $arcnos($p)
8829     if {[llength $a] != 1} {
8830         puts "oops splitarc called but [llength $a] arcs already"
8831         return
8832     }
8833     set a [lindex $a 0]
8834     set i [lsearch -exact $arcids($a) $p]
8835     if {$i < 0} {
8836         puts "oops splitarc $p not in arc $a"
8837         return
8838     }
8839     set na [incr nextarc]
8840     if {[info exists arcend($a)]} {
8841         set arcend($na) $arcend($a)
8842     } else {
8843         set l [lindex $allparents([lindex $arcids($a) end]) 0]
8844         set j [lsearch -exact $arcnos($l) $a]
8845         set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8846     }
8847     set tail [lrange $arcids($a) [expr {$i+1}] end]
8848     set arcids($a) [lrange $arcids($a) 0 $i]
8849     set arcend($a) $p
8850     set arcstart($na) $p
8851     set arcout($p) $na
8852     set arcids($na) $tail
8853     if {[info exists growing($a)]} {
8854         set growing($na) 1
8855         unset growing($a)
8856     }
8857
8858     foreach id $tail {
8859         if {[llength $arcnos($id)] == 1} {
8860             set arcnos($id) $na
8861         } else {
8862             set j [lsearch -exact $arcnos($id) $a]
8863             set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8864         }
8865     }
8866
8867     # reconstruct tags and heads lists
8868     if {$arctags($a) ne {} || $archeads($a) ne {}} {
8869         recalcarc $a
8870         recalcarc $na
8871     } else {
8872         set arctags($na) {}
8873         set archeads($na) {}
8874     }
8875 }
8876
8877 # Update things for a new commit added that is a child of one
8878 # existing commit.  Used when cherry-picking.
8879 proc addnewchild {id p} {
8880     global allparents allchildren idtags nextarc
8881     global arcnos arcids arctags arcout arcend arcstart archeads growing
8882     global seeds allcommits
8883
8884     if {![info exists allcommits] || ![info exists arcnos($p)]} return
8885     set allparents($id) [list $p]
8886     set allchildren($id) {}
8887     set arcnos($id) {}
8888     lappend seeds $id
8889     lappend allchildren($p) $id
8890     set a [incr nextarc]
8891     set arcstart($a) $id
8892     set archeads($a) {}
8893     set arctags($a) {}
8894     set arcids($a) [list $p]
8895     set arcend($a) $p
8896     if {![info exists arcout($p)]} {
8897         splitarc $p
8898     }
8899     lappend arcnos($p) $a
8900     set arcout($id) [list $a]
8901 }
8902
8903 # This implements a cache for the topology information.
8904 # The cache saves, for each arc, the start and end of the arc,
8905 # the ids on the arc, and the outgoing arcs from the end.
8906 proc readcache {f} {
8907     global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8908     global idtags idheads allparents cachedarcs possible_seeds seeds growing
8909     global allcwait
8910
8911     set a $nextarc
8912     set lim $cachedarcs
8913     if {$lim - $a > 500} {
8914         set lim [expr {$a + 500}]
8915     }
8916     if {[catch {
8917         if {$a == $lim} {
8918             # finish reading the cache and setting up arctags, etc.
8919             set line [gets $f]
8920             if {$line ne "1"} {error "bad final version"}
8921             close $f
8922             foreach id [array names idtags] {
8923                 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8924                     [llength $allparents($id)] == 1} {
8925                     set a [lindex $arcnos($id) 0]
8926                     if {$arctags($a) eq {}} {
8927                         recalcarc $a
8928                     }
8929                 }
8930             }
8931             foreach id [array names idheads] {
8932                 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8933                     [llength $allparents($id)] == 1} {
8934                     set a [lindex $arcnos($id) 0]
8935                     if {$archeads($a) eq {}} {
8936                         recalcarc $a
8937                     }
8938                 }
8939             }
8940             foreach id [lsort -unique $possible_seeds] {
8941                 if {$arcnos($id) eq {}} {
8942                     lappend seeds $id
8943                 }
8944             }
8945             set allcwait 0
8946         } else {
8947             while {[incr a] <= $lim} {
8948                 set line [gets $f]
8949                 if {[llength $line] != 3} {error "bad line"}
8950                 set s [lindex $line 0]
8951                 set arcstart($a) $s
8952                 lappend arcout($s) $a
8953                 if {![info exists arcnos($s)]} {
8954                     lappend possible_seeds $s
8955                     set arcnos($s) {}
8956                 }
8957                 set e [lindex $line 1]
8958                 if {$e eq {}} {
8959                     set growing($a) 1
8960                 } else {
8961                     set arcend($a) $e
8962                     if {![info exists arcout($e)]} {
8963                         set arcout($e) {}
8964                     }
8965                 }
8966                 set arcids($a) [lindex $line 2]
8967                 foreach id $arcids($a) {
8968                     lappend allparents($s) $id
8969                     set s $id
8970                     lappend arcnos($id) $a
8971                 }
8972                 if {![info exists allparents($s)]} {
8973                     set allparents($s) {}
8974                 }
8975                 set arctags($a) {}
8976                 set archeads($a) {}
8977             }
8978             set nextarc [expr {$a - 1}]
8979         }
8980     } err]} {
8981         dropcache $err
8982         return 0
8983     }
8984     if {!$allcwait} {
8985         getallcommits
8986     }
8987     return $allcwait
8988 }
8989
8990 proc getcache {f} {
8991     global nextarc cachedarcs possible_seeds
8992
8993     if {[catch {
8994         set line [gets $f]
8995         if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
8996         # make sure it's an integer
8997         set cachedarcs [expr {int([lindex $line 1])}]
8998         if {$cachedarcs < 0} {error "bad number of arcs"}
8999         set nextarc 0
9000         set possible_seeds {}
9001         run readcache $f
9002     } err]} {
9003         dropcache $err
9004     }
9005     return 0
9006 }
9007
9008 proc dropcache {err} {
9009     global allcwait nextarc cachedarcs seeds
9010
9011     #puts "dropping cache ($err)"
9012     foreach v {arcnos arcout arcids arcstart arcend growing \
9013                    arctags archeads allparents allchildren} {
9014         global $v
9015         catch {unset $v}
9016     }
9017     set allcwait 0
9018     set nextarc 0
9019     set cachedarcs 0
9020     set seeds {}
9021     getallcommits
9022 }
9023
9024 proc writecache {f} {
9025     global cachearc cachedarcs allccache
9026     global arcstart arcend arcnos arcids arcout
9027
9028     set a $cachearc
9029     set lim $cachedarcs
9030     if {$lim - $a > 1000} {
9031         set lim [expr {$a + 1000}]
9032     }
9033     if {[catch {
9034         while {[incr a] <= $lim} {
9035             if {[info exists arcend($a)]} {
9036                 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
9037             } else {
9038                 puts $f [list $arcstart($a) {} $arcids($a)]
9039             }
9040         }
9041     } err]} {
9042         catch {close $f}
9043         catch {file delete $allccache}
9044         #puts "writing cache failed ($err)"
9045         return 0
9046     }
9047     set cachearc [expr {$a - 1}]
9048     if {$a > $cachedarcs} {
9049         puts $f "1"
9050         close $f
9051         return 0
9052     }
9053     return 1
9054 }
9055
9056 proc savecache {} {
9057     global nextarc cachedarcs cachearc allccache
9058
9059     if {$nextarc == $cachedarcs} return
9060     set cachearc 0
9061     set cachedarcs $nextarc
9062     catch {
9063         set f [open $allccache w]
9064         puts $f [list 1 $cachedarcs]
9065         run writecache $f
9066     }
9067 }
9068
9069 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
9070 # or 0 if neither is true.
9071 proc anc_or_desc {a b} {
9072     global arcout arcstart arcend arcnos cached_isanc
9073
9074     if {$arcnos($a) eq $arcnos($b)} {
9075         # Both are on the same arc(s); either both are the same BMP,
9076         # or if one is not a BMP, the other is also not a BMP or is
9077         # the BMP at end of the arc (and it only has 1 incoming arc).
9078         # Or both can be BMPs with no incoming arcs.
9079         if {$a eq $b || $arcnos($a) eq {}} {
9080             return 0
9081         }
9082         # assert {[llength $arcnos($a)] == 1}
9083         set arc [lindex $arcnos($a) 0]
9084         set i [lsearch -exact $arcids($arc) $a]
9085         set j [lsearch -exact $arcids($arc) $b]
9086         if {$i < 0 || $i > $j} {
9087             return 1
9088         } else {
9089             return -1
9090         }
9091     }
9092
9093     if {![info exists arcout($a)]} {
9094         set arc [lindex $arcnos($a) 0]
9095         if {[info exists arcend($arc)]} {
9096             set aend $arcend($arc)
9097         } else {
9098             set aend {}
9099         }
9100         set a $arcstart($arc)
9101     } else {
9102         set aend $a
9103     }
9104     if {![info exists arcout($b)]} {
9105         set arc [lindex $arcnos($b) 0]
9106         if {[info exists arcend($arc)]} {
9107             set bend $arcend($arc)
9108         } else {
9109             set bend {}
9110         }
9111         set b $arcstart($arc)
9112     } else {
9113         set bend $b
9114     }
9115     if {$a eq $bend} {
9116         return 1
9117     }
9118     if {$b eq $aend} {
9119         return -1
9120     }
9121     if {[info exists cached_isanc($a,$bend)]} {
9122         if {$cached_isanc($a,$bend)} {
9123             return 1
9124         }
9125     }
9126     if {[info exists cached_isanc($b,$aend)]} {
9127         if {$cached_isanc($b,$aend)} {
9128             return -1
9129         }
9130         if {[info exists cached_isanc($a,$bend)]} {
9131             return 0
9132         }
9133     }
9134
9135     set todo [list $a $b]
9136     set anc($a) a
9137     set anc($b) b
9138     for {set i 0} {$i < [llength $todo]} {incr i} {
9139         set x [lindex $todo $i]
9140         if {$anc($x) eq {}} {
9141             continue
9142         }
9143         foreach arc $arcnos($x) {
9144             set xd $arcstart($arc)
9145             if {$xd eq $bend} {
9146                 set cached_isanc($a,$bend) 1
9147                 set cached_isanc($b,$aend) 0
9148                 return 1
9149             } elseif {$xd eq $aend} {
9150                 set cached_isanc($b,$aend) 1
9151                 set cached_isanc($a,$bend) 0
9152                 return -1
9153             }
9154             if {![info exists anc($xd)]} {
9155                 set anc($xd) $anc($x)
9156                 lappend todo $xd
9157             } elseif {$anc($xd) ne $anc($x)} {
9158                 set anc($xd) {}
9159             }
9160         }
9161     }
9162     set cached_isanc($a,$bend) 0
9163     set cached_isanc($b,$aend) 0
9164     return 0
9165 }
9166
9167 # This identifies whether $desc has an ancestor that is
9168 # a growing tip of the graph and which is not an ancestor of $anc
9169 # and returns 0 if so and 1 if not.
9170 # If we subsequently discover a tag on such a growing tip, and that
9171 # turns out to be a descendent of $anc (which it could, since we
9172 # don't necessarily see children before parents), then $desc
9173 # isn't a good choice to display as a descendent tag of
9174 # $anc (since it is the descendent of another tag which is
9175 # a descendent of $anc).  Similarly, $anc isn't a good choice to
9176 # display as a ancestor tag of $desc.
9177 #
9178 proc is_certain {desc anc} {
9179     global arcnos arcout arcstart arcend growing problems
9180
9181     set certain {}
9182     if {[llength $arcnos($anc)] == 1} {
9183         # tags on the same arc are certain
9184         if {$arcnos($desc) eq $arcnos($anc)} {
9185             return 1
9186         }
9187         if {![info exists arcout($anc)]} {
9188             # if $anc is partway along an arc, use the start of the arc instead
9189             set a [lindex $arcnos($anc) 0]
9190             set anc $arcstart($a)
9191         }
9192     }
9193     if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
9194         set x $desc
9195     } else {
9196         set a [lindex $arcnos($desc) 0]
9197         set x $arcend($a)
9198     }
9199     if {$x == $anc} {
9200         return 1
9201     }
9202     set anclist [list $x]
9203     set dl($x) 1
9204     set nnh 1
9205     set ngrowanc 0
9206     for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
9207         set x [lindex $anclist $i]
9208         if {$dl($x)} {
9209             incr nnh -1
9210         }
9211         set done($x) 1
9212         foreach a $arcout($x) {
9213             if {[info exists growing($a)]} {
9214                 if {![info exists growanc($x)] && $dl($x)} {
9215                     set growanc($x) 1
9216                     incr ngrowanc
9217                 }
9218             } else {
9219                 set y $arcend($a)
9220                 if {[info exists dl($y)]} {
9221                     if {$dl($y)} {
9222                         if {!$dl($x)} {
9223                             set dl($y) 0
9224                             if {![info exists done($y)]} {
9225                                 incr nnh -1
9226                             }
9227                             if {[info exists growanc($x)]} {
9228                                 incr ngrowanc -1
9229                             }
9230                             set xl [list $y]
9231                             for {set k 0} {$k < [llength $xl]} {incr k} {
9232                                 set z [lindex $xl $k]
9233                                 foreach c $arcout($z) {
9234                                     if {[info exists arcend($c)]} {
9235                                         set v $arcend($c)
9236                                         if {[info exists dl($v)] && $dl($v)} {
9237                                             set dl($v) 0
9238                                             if {![info exists done($v)]} {
9239                                                 incr nnh -1
9240                                             }
9241                                             if {[info exists growanc($v)]} {
9242                                                 incr ngrowanc -1
9243                                             }
9244                                             lappend xl $v
9245                                         }
9246                                     }
9247                                 }
9248                             }
9249                         }
9250                     }
9251                 } elseif {$y eq $anc || !$dl($x)} {
9252                     set dl($y) 0
9253                     lappend anclist $y
9254                 } else {
9255                     set dl($y) 1
9256                     lappend anclist $y
9257                     incr nnh
9258                 }
9259             }
9260         }
9261     }
9262     foreach x [array names growanc] {
9263         if {$dl($x)} {
9264             return 0
9265         }
9266         return 0
9267     }
9268     return 1
9269 }
9270
9271 proc validate_arctags {a} {
9272     global arctags idtags
9273
9274     set i -1
9275     set na $arctags($a)
9276     foreach id $arctags($a) {
9277         incr i
9278         if {![info exists idtags($id)]} {
9279             set na [lreplace $na $i $i]
9280             incr i -1
9281         }
9282     }
9283     set arctags($a) $na
9284 }
9285
9286 proc validate_archeads {a} {
9287     global archeads idheads
9288
9289     set i -1
9290     set na $archeads($a)
9291     foreach id $archeads($a) {
9292         incr i
9293         if {![info exists idheads($id)]} {
9294             set na [lreplace $na $i $i]
9295             incr i -1
9296         }
9297     }
9298     set archeads($a) $na
9299 }
9300
9301 # Return the list of IDs that have tags that are descendents of id,
9302 # ignoring IDs that are descendents of IDs already reported.
9303 proc desctags {id} {
9304     global arcnos arcstart arcids arctags idtags allparents
9305     global growing cached_dtags
9306
9307     if {![info exists allparents($id)]} {
9308         return {}
9309     }
9310     set t1 [clock clicks -milliseconds]
9311     set argid $id
9312     if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9313         # part-way along an arc; check that arc first
9314         set a [lindex $arcnos($id) 0]
9315         if {$arctags($a) ne {}} {
9316             validate_arctags $a
9317             set i [lsearch -exact $arcids($a) $id]
9318             set tid {}
9319             foreach t $arctags($a) {
9320                 set j [lsearch -exact $arcids($a) $t]
9321                 if {$j >= $i} break
9322                 set tid $t
9323             }
9324             if {$tid ne {}} {
9325                 return $tid
9326             }
9327         }
9328         set id $arcstart($a)
9329         if {[info exists idtags($id)]} {
9330             return $id
9331         }
9332     }
9333     if {[info exists cached_dtags($id)]} {
9334         return $cached_dtags($id)
9335     }
9336
9337     set origid $id
9338     set todo [list $id]
9339     set queued($id) 1
9340     set nc 1
9341     for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9342         set id [lindex $todo $i]
9343         set done($id) 1
9344         set ta [info exists hastaggedancestor($id)]
9345         if {!$ta} {
9346             incr nc -1
9347         }
9348         # ignore tags on starting node
9349         if {!$ta && $i > 0} {
9350             if {[info exists idtags($id)]} {
9351                 set tagloc($id) $id
9352                 set ta 1
9353             } elseif {[info exists cached_dtags($id)]} {
9354                 set tagloc($id) $cached_dtags($id)
9355                 set ta 1
9356             }
9357         }
9358         foreach a $arcnos($id) {
9359             set d $arcstart($a)
9360             if {!$ta && $arctags($a) ne {}} {
9361                 validate_arctags $a
9362                 if {$arctags($a) ne {}} {
9363                     lappend tagloc($id) [lindex $arctags($a) end]
9364                 }
9365             }
9366             if {$ta || $arctags($a) ne {}} {
9367                 set tomark [list $d]
9368                 for {set j 0} {$j < [llength $tomark]} {incr j} {
9369                     set dd [lindex $tomark $j]
9370                     if {![info exists hastaggedancestor($dd)]} {
9371                         if {[info exists done($dd)]} {
9372                             foreach b $arcnos($dd) {
9373                                 lappend tomark $arcstart($b)
9374                             }
9375                             if {[info exists tagloc($dd)]} {
9376                                 unset tagloc($dd)
9377                             }
9378                         } elseif {[info exists queued($dd)]} {
9379                             incr nc -1
9380                         }
9381                         set hastaggedancestor($dd) 1
9382                     }
9383                 }
9384             }
9385             if {![info exists queued($d)]} {
9386                 lappend todo $d
9387                 set queued($d) 1
9388                 if {![info exists hastaggedancestor($d)]} {
9389                     incr nc
9390                 }
9391             }
9392         }
9393     }
9394     set tags {}
9395     foreach id [array names tagloc] {
9396         if {![info exists hastaggedancestor($id)]} {
9397             foreach t $tagloc($id) {
9398                 if {[lsearch -exact $tags $t] < 0} {
9399                     lappend tags $t
9400                 }
9401             }
9402         }
9403     }
9404     set t2 [clock clicks -milliseconds]
9405     set loopix $i
9406
9407     # remove tags that are descendents of other tags
9408     for {set i 0} {$i < [llength $tags]} {incr i} {
9409         set a [lindex $tags $i]
9410         for {set j 0} {$j < $i} {incr j} {
9411             set b [lindex $tags $j]
9412             set r [anc_or_desc $a $b]
9413             if {$r == 1} {
9414                 set tags [lreplace $tags $j $j]
9415                 incr j -1
9416                 incr i -1
9417             } elseif {$r == -1} {
9418                 set tags [lreplace $tags $i $i]
9419                 incr i -1
9420                 break
9421             }
9422         }
9423     }
9424
9425     if {[array names growing] ne {}} {
9426         # graph isn't finished, need to check if any tag could get
9427         # eclipsed by another tag coming later.  Simply ignore any
9428         # tags that could later get eclipsed.
9429         set ctags {}
9430         foreach t $tags {
9431             if {[is_certain $t $origid]} {
9432                 lappend ctags $t
9433             }
9434         }
9435         if {$tags eq $ctags} {
9436             set cached_dtags($origid) $tags
9437         } else {
9438             set tags $ctags
9439         }
9440     } else {
9441         set cached_dtags($origid) $tags
9442     }
9443     set t3 [clock clicks -milliseconds]
9444     if {0 && $t3 - $t1 >= 100} {
9445         puts "iterating descendents ($loopix/[llength $todo] nodes) took\
9446             [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9447     }
9448     return $tags
9449 }
9450
9451 proc anctags {id} {
9452     global arcnos arcids arcout arcend arctags idtags allparents
9453     global growing cached_atags
9454
9455     if {![info exists allparents($id)]} {
9456         return {}
9457     }
9458     set t1 [clock clicks -milliseconds]
9459     set argid $id
9460     if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9461         # part-way along an arc; check that arc first
9462         set a [lindex $arcnos($id) 0]
9463         if {$arctags($a) ne {}} {
9464             validate_arctags $a
9465             set i [lsearch -exact $arcids($a) $id]
9466             foreach t $arctags($a) {
9467                 set j [lsearch -exact $arcids($a) $t]
9468                 if {$j > $i} {
9469                     return $t
9470                 }
9471             }
9472         }
9473         if {![info exists arcend($a)]} {
9474             return {}
9475         }
9476         set id $arcend($a)
9477         if {[info exists idtags($id)]} {
9478             return $id
9479         }
9480     }
9481     if {[info exists cached_atags($id)]} {
9482         return $cached_atags($id)
9483     }
9484
9485     set origid $id
9486     set todo [list $id]
9487     set queued($id) 1
9488     set taglist {}
9489     set nc 1
9490     for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9491         set id [lindex $todo $i]
9492         set done($id) 1
9493         set td [info exists hastaggeddescendent($id)]
9494         if {!$td} {
9495             incr nc -1
9496         }
9497         # ignore tags on starting node
9498         if {!$td && $i > 0} {
9499             if {[info exists idtags($id)]} {
9500                 set tagloc($id) $id
9501                 set td 1
9502             } elseif {[info exists cached_atags($id)]} {
9503                 set tagloc($id) $cached_atags($id)
9504                 set td 1
9505             }
9506         }
9507         foreach a $arcout($id) {
9508             if {!$td && $arctags($a) ne {}} {
9509                 validate_arctags $a
9510                 if {$arctags($a) ne {}} {
9511                     lappend tagloc($id) [lindex $arctags($a) 0]
9512                 }
9513             }
9514             if {![info exists arcend($a)]} continue
9515             set d $arcend($a)
9516             if {$td || $arctags($a) ne {}} {
9517                 set tomark [list $d]
9518                 for {set j 0} {$j < [llength $tomark]} {incr j} {
9519                     set dd [lindex $tomark $j]
9520                     if {![info exists hastaggeddescendent($dd)]} {
9521                         if {[info exists done($dd)]} {
9522                             foreach b $arcout($dd) {
9523                                 if {[info exists arcend($b)]} {
9524                                     lappend tomark $arcend($b)
9525                                 }
9526                             }
9527                             if {[info exists tagloc($dd)]} {
9528                                 unset tagloc($dd)
9529                             }
9530                         } elseif {[info exists queued($dd)]} {
9531                             incr nc -1
9532                         }
9533                         set hastaggeddescendent($dd) 1
9534                     }
9535                 }
9536             }
9537             if {![info exists queued($d)]} {
9538                 lappend todo $d
9539                 set queued($d) 1
9540                 if {![info exists hastaggeddescendent($d)]} {
9541                     incr nc
9542                 }
9543             }
9544         }
9545     }
9546     set t2 [clock clicks -milliseconds]
9547     set loopix $i
9548     set tags {}
9549     foreach id [array names tagloc] {
9550         if {![info exists hastaggeddescendent($id)]} {
9551             foreach t $tagloc($id) {
9552                 if {[lsearch -exact $tags $t] < 0} {
9553                     lappend tags $t
9554                 }
9555             }
9556         }
9557     }
9558
9559     # remove tags that are ancestors of other tags
9560     for {set i 0} {$i < [llength $tags]} {incr i} {
9561         set a [lindex $tags $i]
9562         for {set j 0} {$j < $i} {incr j} {
9563             set b [lindex $tags $j]
9564             set r [anc_or_desc $a $b]
9565             if {$r == -1} {
9566                 set tags [lreplace $tags $j $j]
9567                 incr j -1
9568                 incr i -1
9569             } elseif {$r == 1} {
9570                 set tags [lreplace $tags $i $i]
9571                 incr i -1
9572                 break
9573             }
9574         }
9575     }
9576
9577     if {[array names growing] ne {}} {
9578         # graph isn't finished, need to check if any tag could get
9579         # eclipsed by another tag coming later.  Simply ignore any
9580         # tags that could later get eclipsed.
9581         set ctags {}
9582         foreach t $tags {
9583             if {[is_certain $origid $t]} {
9584                 lappend ctags $t
9585             }
9586         }
9587         if {$tags eq $ctags} {
9588             set cached_atags($origid) $tags
9589         } else {
9590             set tags $ctags
9591         }
9592     } else {
9593         set cached_atags($origid) $tags
9594     }
9595     set t3 [clock clicks -milliseconds]
9596     if {0 && $t3 - $t1 >= 100} {
9597         puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
9598             [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9599     }
9600     return $tags
9601 }
9602
9603 # Return the list of IDs that have heads that are descendents of id,
9604 # including id itself if it has a head.
9605 proc descheads {id} {
9606     global arcnos arcstart arcids archeads idheads cached_dheads
9607     global allparents
9608
9609     if {![info exists allparents($id)]} {
9610         return {}
9611     }
9612     set aret {}
9613     if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9614         # part-way along an arc; check it first
9615         set a [lindex $arcnos($id) 0]
9616         if {$archeads($a) ne {}} {
9617             validate_archeads $a
9618             set i [lsearch -exact $arcids($a) $id]
9619             foreach t $archeads($a) {
9620                 set j [lsearch -exact $arcids($a) $t]
9621                 if {$j > $i} break
9622                 lappend aret $t
9623             }
9624         }
9625         set id $arcstart($a)
9626     }
9627     set origid $id
9628     set todo [list $id]
9629     set seen($id) 1
9630     set ret {}
9631     for {set i 0} {$i < [llength $todo]} {incr i} {
9632         set id [lindex $todo $i]
9633         if {[info exists cached_dheads($id)]} {
9634             set ret [concat $ret $cached_dheads($id)]
9635         } else {
9636             if {[info exists idheads($id)]} {
9637                 lappend ret $id
9638             }
9639             foreach a $arcnos($id) {
9640                 if {$archeads($a) ne {}} {
9641                     validate_archeads $a
9642                     if {$archeads($a) ne {}} {
9643                         set ret [concat $ret $archeads($a)]
9644                     }
9645                 }
9646                 set d $arcstart($a)
9647                 if {![info exists seen($d)]} {
9648                     lappend todo $d
9649                     set seen($d) 1
9650                 }
9651             }
9652         }
9653     }
9654     set ret [lsort -unique $ret]
9655     set cached_dheads($origid) $ret
9656     return [concat $ret $aret]
9657 }
9658
9659 proc addedtag {id} {
9660     global arcnos arcout cached_dtags cached_atags
9661
9662     if {![info exists arcnos($id)]} return
9663     if {![info exists arcout($id)]} {
9664         recalcarc [lindex $arcnos($id) 0]
9665     }
9666     catch {unset cached_dtags}
9667     catch {unset cached_atags}
9668 }
9669
9670 proc addedhead {hid head} {
9671     global arcnos arcout cached_dheads
9672
9673     if {![info exists arcnos($hid)]} return
9674     if {![info exists arcout($hid)]} {
9675         recalcarc [lindex $arcnos($hid) 0]
9676     }
9677     catch {unset cached_dheads}
9678 }
9679
9680 proc removedhead {hid head} {
9681     global cached_dheads
9682
9683     catch {unset cached_dheads}
9684 }
9685
9686 proc movedhead {hid head} {
9687     global arcnos arcout cached_dheads
9688
9689     if {![info exists arcnos($hid)]} return
9690     if {![info exists arcout($hid)]} {
9691         recalcarc [lindex $arcnos($hid) 0]
9692     }
9693     catch {unset cached_dheads}
9694 }
9695
9696 proc changedrefs {} {
9697     global cached_dheads cached_dtags cached_atags
9698     global arctags archeads arcnos arcout idheads idtags
9699
9700     foreach id [concat [array names idheads] [array names idtags]] {
9701         if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9702             set a [lindex $arcnos($id) 0]
9703             if {![info exists donearc($a)]} {
9704                 recalcarc $a
9705                 set donearc($a) 1
9706             }
9707         }
9708     }
9709     catch {unset cached_dtags}
9710     catch {unset cached_atags}
9711     catch {unset cached_dheads}
9712 }
9713
9714 proc rereadrefs {} {
9715     global idtags idheads idotherrefs mainheadid
9716
9717     set refids [concat [array names idtags] \
9718                     [array names idheads] [array names idotherrefs]]
9719     foreach id $refids {
9720         if {![info exists ref($id)]} {
9721             set ref($id) [listrefs $id]
9722         }
9723     }
9724     set oldmainhead $mainheadid
9725     readrefs
9726     changedrefs
9727     set refids [lsort -unique [concat $refids [array names idtags] \
9728                         [array names idheads] [array names idotherrefs]]]
9729     foreach id $refids {
9730         set v [listrefs $id]
9731         if {![info exists ref($id)] || $ref($id) != $v} {
9732             redrawtags $id
9733         }
9734     }
9735     if {$oldmainhead ne $mainheadid} {
9736         redrawtags $oldmainhead
9737         redrawtags $mainheadid
9738     }
9739     run refill_reflist
9740 }
9741
9742 proc listrefs {id} {
9743     global idtags idheads idotherrefs
9744
9745     set x {}
9746     if {[info exists idtags($id)]} {
9747         set x $idtags($id)
9748     }
9749     set y {}
9750     if {[info exists idheads($id)]} {
9751         set y $idheads($id)
9752     }
9753     set z {}
9754     if {[info exists idotherrefs($id)]} {
9755         set z $idotherrefs($id)
9756     }
9757     return [list $x $y $z]
9758 }
9759
9760 proc showtag {tag isnew} {
9761     global ctext tagcontents tagids linknum tagobjid
9762
9763     if {$isnew} {
9764         addtohistory [list showtag $tag 0]
9765     }
9766     $ctext conf -state normal
9767     clear_ctext
9768     settabs 0
9769     set linknum 0
9770     if {![info exists tagcontents($tag)]} {
9771         catch {
9772             set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9773         }
9774     }
9775     if {[info exists tagcontents($tag)]} {
9776         set text $tagcontents($tag)
9777     } else {
9778         set text "[mc "Tag"]: $tag\n[mc "Id"]:  $tagids($tag)"
9779     }
9780     appendwithlinks $text {}
9781     $ctext conf -state disabled
9782     init_flist {}
9783 }
9784
9785 proc doquit {} {
9786     global stopped
9787     global gitktmpdir
9788
9789     set stopped 100
9790     savestuff .
9791     destroy .
9792
9793     if {[info exists gitktmpdir]} {
9794         catch {file delete -force $gitktmpdir}
9795     }
9796 }
9797
9798 proc mkfontdisp {font top which} {
9799     global fontattr fontpref $font
9800
9801     set fontpref($font) [set $font]
9802     button $top.${font}but -text $which -font optionfont \
9803         -command [list choosefont $font $which]
9804     label $top.$font -relief flat -font $font \
9805         -text $fontattr($font,family) -justify left
9806     grid x $top.${font}but $top.$font -sticky w
9807 }
9808
9809 proc choosefont {font which} {
9810     global fontparam fontlist fonttop fontattr
9811     global prefstop
9812
9813     set fontparam(which) $which
9814     set fontparam(font) $font
9815     set fontparam(family) [font actual $font -family]
9816     set fontparam(size) $fontattr($font,size)
9817     set fontparam(weight) $fontattr($font,weight)
9818     set fontparam(slant) $fontattr($font,slant)
9819     set top .gitkfont
9820     set fonttop $top
9821     if {![winfo exists $top]} {
9822         font create sample
9823         eval font config sample [font actual $font]
9824         toplevel $top
9825         wm transient $top $prefstop
9826         wm title $top [mc "Gitk font chooser"]
9827         label $top.l -textvariable fontparam(which)
9828         pack $top.l -side top
9829         set fontlist [lsort [font families]]
9830         frame $top.f
9831         listbox $top.f.fam -listvariable fontlist \
9832             -yscrollcommand [list $top.f.sb set]
9833         bind $top.f.fam <<ListboxSelect>> selfontfam
9834         scrollbar $top.f.sb -command [list $top.f.fam yview]
9835         pack $top.f.sb -side right -fill y
9836         pack $top.f.fam -side left -fill both -expand 1
9837         pack $top.f -side top -fill both -expand 1
9838         frame $top.g
9839         spinbox $top.g.size -from 4 -to 40 -width 4 \
9840             -textvariable fontparam(size) \
9841             -validatecommand {string is integer -strict %s}
9842         checkbutton $top.g.bold -padx 5 \
9843             -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9844             -variable fontparam(weight) -onvalue bold -offvalue normal
9845         checkbutton $top.g.ital -padx 5 \
9846             -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0  \
9847             -variable fontparam(slant) -onvalue italic -offvalue roman
9848         pack $top.g.size $top.g.bold $top.g.ital -side left
9849         pack $top.g -side top
9850         canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9851             -background white
9852         $top.c create text 100 25 -anchor center -text $which -font sample \
9853             -fill black -tags text
9854         bind $top.c <Configure> [list centertext $top.c]
9855         pack $top.c -side top -fill x
9856         frame $top.buts
9857         button $top.buts.ok -text [mc "OK"] -command fontok -default active
9858         button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9859         bind $top <Key-Return> fontok
9860         bind $top <Key-Escape> fontcan
9861         grid $top.buts.ok $top.buts.can
9862         grid columnconfigure $top.buts 0 -weight 1 -uniform a
9863         grid columnconfigure $top.buts 1 -weight 1 -uniform a
9864         pack $top.buts -side bottom -fill x
9865         trace add variable fontparam write chg_fontparam
9866     } else {
9867         raise $top
9868         $top.c itemconf text -text $which
9869     }
9870     set i [lsearch -exact $fontlist $fontparam(family)]
9871     if {$i >= 0} {
9872         $top.f.fam selection set $i
9873         $top.f.fam see $i
9874     }
9875 }
9876
9877 proc centertext {w} {
9878     $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9879 }
9880
9881 proc fontok {} {
9882     global fontparam fontpref prefstop
9883
9884     set f $fontparam(font)
9885     set fontpref($f) [list $fontparam(family) $fontparam(size)]
9886     if {$fontparam(weight) eq "bold"} {
9887         lappend fontpref($f) "bold"
9888     }
9889     if {$fontparam(slant) eq "italic"} {
9890         lappend fontpref($f) "italic"
9891     }
9892     set w $prefstop.$f
9893     $w conf -text $fontparam(family) -font $fontpref($f)
9894         
9895     fontcan
9896 }
9897
9898 proc fontcan {} {
9899     global fonttop fontparam
9900
9901     if {[info exists fonttop]} {
9902         catch {destroy $fonttop}
9903         catch {font delete sample}
9904         unset fonttop
9905         unset fontparam
9906     }
9907 }
9908
9909 proc selfontfam {} {
9910     global fonttop fontparam
9911
9912     set i [$fonttop.f.fam curselection]
9913     if {$i ne {}} {
9914         set fontparam(family) [$fonttop.f.fam get $i]
9915     }
9916 }
9917
9918 proc chg_fontparam {v sub op} {
9919     global fontparam
9920
9921     font config sample -$sub $fontparam($sub)
9922 }
9923
9924 proc doprefs {} {
9925     global maxwidth maxgraphpct
9926     global oldprefs prefstop showneartags showlocalchanges
9927     global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
9928     global tabstop limitdiffs autoselect extdifftool perfile_attrs
9929
9930     set top .gitkprefs
9931     set prefstop $top
9932     if {[winfo exists $top]} {
9933         raise $top
9934         return
9935     }
9936     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9937                    limitdiffs tabstop perfile_attrs} {
9938         set oldprefs($v) [set $v]
9939     }
9940     toplevel $top
9941     wm title $top [mc "Gitk preferences"]
9942     wm transient $top .
9943     label $top.ldisp -text [mc "Commit list display options"]
9944     grid $top.ldisp - -sticky w -pady 10
9945     label $top.spacer -text " "
9946     label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9947         -font optionfont
9948     spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9949     grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9950     label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9951         -font optionfont
9952     spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9953     grid x $top.maxpctl $top.maxpct -sticky w
9954     frame $top.showlocal
9955     label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9956     checkbutton $top.showlocal.b -variable showlocalchanges
9957     pack $top.showlocal.b $top.showlocal.l -side left
9958     grid x $top.showlocal -sticky w
9959     frame $top.autoselect
9960     label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9961     checkbutton $top.autoselect.b -variable autoselect
9962     pack $top.autoselect.b $top.autoselect.l -side left
9963     grid x $top.autoselect -sticky w
9964
9965     label $top.ddisp -text [mc "Diff display options"]
9966     grid $top.ddisp - -sticky w -pady 10
9967     label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9968     spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9969     grid x $top.tabstopl $top.tabstop -sticky w
9970     frame $top.ntag
9971     label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9972     checkbutton $top.ntag.b -variable showneartags
9973     pack $top.ntag.b $top.ntag.l -side left
9974     grid x $top.ntag -sticky w
9975     frame $top.ldiff
9976     label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9977     checkbutton $top.ldiff.b -variable limitdiffs
9978     pack $top.ldiff.b $top.ldiff.l -side left
9979     grid x $top.ldiff -sticky w
9980     frame $top.lattr
9981     label $top.lattr.l -text [mc "Support per-file encodings"] -font optionfont
9982     checkbutton $top.lattr.b -variable perfile_attrs
9983     pack $top.lattr.b $top.lattr.l -side left
9984     grid x $top.lattr -sticky w
9985
9986     entry $top.extdifft -textvariable extdifftool
9987     frame $top.extdifff
9988     label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
9989         -padx 10
9990     button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
9991         -command choose_extdiff
9992     pack $top.extdifff.l $top.extdifff.b -side left
9993     grid x $top.extdifff $top.extdifft -sticky w
9994
9995     label $top.cdisp -text [mc "Colors: press to choose"]
9996     grid $top.cdisp - -sticky w -pady 10
9997     label $top.bg -padx 40 -relief sunk -background $bgcolor
9998     button $top.bgbut -text [mc "Background"] -font optionfont \
9999         -command [list choosecolor bgcolor {} $top.bg background setbg]
10000     grid x $top.bgbut $top.bg -sticky w
10001     label $top.fg -padx 40 -relief sunk -background $fgcolor
10002     button $top.fgbut -text [mc "Foreground"] -font optionfont \
10003         -command [list choosecolor fgcolor {} $top.fg foreground setfg]
10004     grid x $top.fgbut $top.fg -sticky w
10005     label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
10006     button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
10007         -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
10008                       [list $ctext tag conf d0 -foreground]]
10009     grid x $top.diffoldbut $top.diffold -sticky w
10010     label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
10011     button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
10012         -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
10013                       [list $ctext tag conf dresult -foreground]]
10014     grid x $top.diffnewbut $top.diffnew -sticky w
10015     label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
10016     button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
10017         -command [list choosecolor diffcolors 2 $top.hunksep \
10018                       "diff hunk header" \
10019                       [list $ctext tag conf hunksep -foreground]]
10020     grid x $top.hunksepbut $top.hunksep -sticky w
10021     label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
10022     button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \
10023         -command [list choosecolor markbgcolor {} $top.markbgsep \
10024                       [mc "marked line background"] \
10025                       [list $ctext tag conf omark -background]]
10026     grid x $top.markbgbut $top.markbgsep -sticky w
10027     label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
10028     button $top.selbgbut -text [mc "Select bg"] -font optionfont \
10029         -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
10030     grid x $top.selbgbut $top.selbgsep -sticky w
10031
10032     label $top.cfont -text [mc "Fonts: press to choose"]
10033     grid $top.cfont - -sticky w -pady 10
10034     mkfontdisp mainfont $top [mc "Main font"]
10035     mkfontdisp textfont $top [mc "Diff display font"]
10036     mkfontdisp uifont $top [mc "User interface font"]
10037
10038     frame $top.buts
10039     button $top.buts.ok -text [mc "OK"] -command prefsok -default active
10040     button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
10041     bind $top <Key-Return> prefsok
10042     bind $top <Key-Escape> prefscan
10043     grid $top.buts.ok $top.buts.can
10044     grid columnconfigure $top.buts 0 -weight 1 -uniform a
10045     grid columnconfigure $top.buts 1 -weight 1 -uniform a
10046     grid $top.buts - - -pady 10 -sticky ew
10047     bind $top <Visibility> "focus $top.buts.ok"
10048 }
10049
10050 proc choose_extdiff {} {
10051     global extdifftool
10052
10053     set prog [tk_getOpenFile -title "External diff tool" -multiple false]
10054     if {$prog ne {}} {
10055         set extdifftool $prog
10056     }
10057 }
10058
10059 proc choosecolor {v vi w x cmd} {
10060     global $v
10061
10062     set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
10063                -title [mc "Gitk: choose color for %s" $x]]
10064     if {$c eq {}} return
10065     $w conf -background $c
10066     lset $v $vi $c
10067     eval $cmd $c
10068 }
10069
10070 proc setselbg {c} {
10071     global bglist cflist
10072     foreach w $bglist {
10073         $w configure -selectbackground $c
10074     }
10075     $cflist tag configure highlight \
10076         -background [$cflist cget -selectbackground]
10077     allcanvs itemconf secsel -fill $c
10078 }
10079
10080 proc setbg {c} {
10081     global bglist
10082
10083     foreach w $bglist {
10084         $w conf -background $c
10085     }
10086 }
10087
10088 proc setfg {c} {
10089     global fglist canv
10090
10091     foreach w $fglist {
10092         $w conf -foreground $c
10093     }
10094     allcanvs itemconf text -fill $c
10095     $canv itemconf circle -outline $c
10096 }
10097
10098 proc prefscan {} {
10099     global oldprefs prefstop
10100
10101     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10102                    limitdiffs tabstop perfile_attrs} {
10103         global $v
10104         set $v $oldprefs($v)
10105     }
10106     catch {destroy $prefstop}
10107     unset prefstop
10108     fontcan
10109 }
10110
10111 proc prefsok {} {
10112     global maxwidth maxgraphpct
10113     global oldprefs prefstop showneartags showlocalchanges
10114     global fontpref mainfont textfont uifont
10115     global limitdiffs treediffs perfile_attrs
10116
10117     catch {destroy $prefstop}
10118     unset prefstop
10119     fontcan
10120     set fontchanged 0
10121     if {$mainfont ne $fontpref(mainfont)} {
10122         set mainfont $fontpref(mainfont)
10123         parsefont mainfont $mainfont
10124         eval font configure mainfont [fontflags mainfont]
10125         eval font configure mainfontbold [fontflags mainfont 1]
10126         setcoords
10127         set fontchanged 1
10128     }
10129     if {$textfont ne $fontpref(textfont)} {
10130         set textfont $fontpref(textfont)
10131         parsefont textfont $textfont
10132         eval font configure textfont [fontflags textfont]
10133         eval font configure textfontbold [fontflags textfont 1]
10134     }
10135     if {$uifont ne $fontpref(uifont)} {
10136         set uifont $fontpref(uifont)
10137         parsefont uifont $uifont
10138         eval font configure uifont [fontflags uifont]
10139     }
10140     settabs
10141     if {$showlocalchanges != $oldprefs(showlocalchanges)} {
10142         if {$showlocalchanges} {
10143             doshowlocalchanges
10144         } else {
10145             dohidelocalchanges
10146         }
10147     }
10148     if {$limitdiffs != $oldprefs(limitdiffs) ||
10149         ($perfile_attrs && !$oldprefs(perfile_attrs))} {
10150         # treediffs elements are limited by path;
10151         # won't have encodings cached if perfile_attrs was just turned on
10152         catch {unset treediffs}
10153     }
10154     if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
10155         || $maxgraphpct != $oldprefs(maxgraphpct)} {
10156         redisplay
10157     } elseif {$showneartags != $oldprefs(showneartags) ||
10158           $limitdiffs != $oldprefs(limitdiffs)} {
10159         reselectline
10160     }
10161 }
10162
10163 proc formatdate {d} {
10164     global datetimeformat
10165     if {$d ne {}} {
10166         set d [clock format $d -format $datetimeformat]
10167     }
10168     return $d
10169 }
10170
10171 # This list of encoding names and aliases is distilled from
10172 # http://www.iana.org/assignments/character-sets.
10173 # Not all of them are supported by Tcl.
10174 set encoding_aliases {
10175     { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
10176       ISO646-US US-ASCII us IBM367 cp367 csASCII }
10177     { ISO-10646-UTF-1 csISO10646UTF1 }
10178     { ISO_646.basic:1983 ref csISO646basic1983 }
10179     { INVARIANT csINVARIANT }
10180     { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
10181     { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
10182     { NATS-SEFI iso-ir-8-1 csNATSSEFI }
10183     { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
10184     { NATS-DANO iso-ir-9-1 csNATSDANO }
10185     { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
10186     { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
10187     { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
10188     { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
10189     { ISO-2022-KR csISO2022KR }
10190     { EUC-KR csEUCKR }
10191     { ISO-2022-JP csISO2022JP }
10192     { ISO-2022-JP-2 csISO2022JP2 }
10193     { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
10194       csISO13JISC6220jp }
10195     { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
10196     { IT iso-ir-15 ISO646-IT csISO15Italian }
10197     { PT iso-ir-16 ISO646-PT csISO16Portuguese }
10198     { ES iso-ir-17 ISO646-ES csISO17Spanish }
10199     { greek7-old iso-ir-18 csISO18Greek7Old }
10200     { latin-greek iso-ir-19 csISO19LatinGreek }
10201     { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
10202     { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
10203     { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
10204     { ISO_5427 iso-ir-37 csISO5427Cyrillic }
10205     { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
10206     { BS_viewdata iso-ir-47 csISO47BSViewdata }
10207     { INIS iso-ir-49 csISO49INIS }
10208     { INIS-8 iso-ir-50 csISO50INIS8 }
10209     { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
10210     { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
10211     { ISO_5428:1980 iso-ir-55 csISO5428Greek }
10212     { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
10213     { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
10214     { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
10215       csISO60Norwegian1 }
10216     { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
10217     { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
10218     { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
10219     { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
10220     { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
10221     { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
10222     { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
10223     { greek7 iso-ir-88 csISO88Greek7 }
10224     { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
10225     { iso-ir-90 csISO90 }
10226     { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
10227     { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
10228       csISO92JISC62991984b }
10229     { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
10230     { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
10231     { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
10232       csISO95JIS62291984handadd }
10233     { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
10234     { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
10235     { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
10236     { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
10237       CP819 csISOLatin1 }
10238     { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
10239     { T.61-7bit iso-ir-102 csISO102T617bit }
10240     { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
10241     { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
10242     { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
10243     { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
10244     { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
10245     { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
10246     { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
10247     { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
10248       arabic csISOLatinArabic }
10249     { ISO_8859-6-E csISO88596E ISO-8859-6-E }
10250     { ISO_8859-6-I csISO88596I ISO-8859-6-I }
10251     { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
10252       greek greek8 csISOLatinGreek }
10253     { T.101-G2 iso-ir-128 csISO128T101G2 }
10254     { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
10255       csISOLatinHebrew }
10256     { ISO_8859-8-E csISO88598E ISO-8859-8-E }
10257     { ISO_8859-8-I csISO88598I ISO-8859-8-I }
10258     { CSN_369103 iso-ir-139 csISO139CSN369103 }
10259     { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
10260     { ISO_6937-2-add iso-ir-142 csISOTextComm }
10261     { IEC_P27-1 iso-ir-143 csISO143IECP271 }
10262     { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
10263       csISOLatinCyrillic }
10264     { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
10265     { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
10266     { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
10267     { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
10268     { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
10269     { ISO_6937-2-25 iso-ir-152 csISO6937Add }
10270     { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
10271     { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
10272     { ISO_10367-box iso-ir-155 csISO10367Box }
10273     { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
10274     { latin-lap lap iso-ir-158 csISO158Lap }
10275     { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
10276     { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
10277     { us-dk csUSDK }
10278     { dk-us csDKUS }
10279     { JIS_X0201 X0201 csHalfWidthKatakana }
10280     { KSC5636 ISO646-KR csKSC5636 }
10281     { ISO-10646-UCS-2 csUnicode }
10282     { ISO-10646-UCS-4 csUCS4 }
10283     { DEC-MCS dec csDECMCS }
10284     { hp-roman8 roman8 r8 csHPRoman8 }
10285     { macintosh mac csMacintosh }
10286     { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
10287       csIBM037 }
10288     { IBM038 EBCDIC-INT cp038 csIBM038 }
10289     { IBM273 CP273 csIBM273 }
10290     { IBM274 EBCDIC-BE CP274 csIBM274 }
10291     { IBM275 EBCDIC-BR cp275 csIBM275 }
10292     { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
10293     { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
10294     { IBM280 CP280 ebcdic-cp-it csIBM280 }
10295     { IBM281 EBCDIC-JP-E cp281 csIBM281 }
10296     { IBM284 CP284 ebcdic-cp-es csIBM284 }
10297     { IBM285 CP285 ebcdic-cp-gb csIBM285 }
10298     { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
10299     { IBM297 cp297 ebcdic-cp-fr csIBM297 }
10300     { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
10301     { IBM423 cp423 ebcdic-cp-gr csIBM423 }
10302     { IBM424 cp424 ebcdic-cp-he csIBM424 }
10303     { IBM437 cp437 437 csPC8CodePage437 }
10304     { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
10305     { IBM775 cp775 csPC775Baltic }
10306     { IBM850 cp850 850 csPC850Multilingual }
10307     { IBM851 cp851 851 csIBM851 }
10308     { IBM852 cp852 852 csPCp852 }
10309     { IBM855 cp855 855 csIBM855 }
10310     { IBM857 cp857 857 csIBM857 }
10311     { IBM860 cp860 860 csIBM860 }
10312     { IBM861 cp861 861 cp-is csIBM861 }
10313     { IBM862 cp862 862 csPC862LatinHebrew }
10314     { IBM863 cp863 863 csIBM863 }
10315     { IBM864 cp864 csIBM864 }
10316     { IBM865 cp865 865 csIBM865 }
10317     { IBM866 cp866 866 csIBM866 }
10318     { IBM868 CP868 cp-ar csIBM868 }
10319     { IBM869 cp869 869 cp-gr csIBM869 }
10320     { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
10321     { IBM871 CP871 ebcdic-cp-is csIBM871 }
10322     { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
10323     { IBM891 cp891 csIBM891 }
10324     { IBM903 cp903 csIBM903 }
10325     { IBM904 cp904 904 csIBBM904 }
10326     { IBM905 CP905 ebcdic-cp-tr csIBM905 }
10327     { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
10328     { IBM1026 CP1026 csIBM1026 }
10329     { EBCDIC-AT-DE csIBMEBCDICATDE }
10330     { EBCDIC-AT-DE-A csEBCDICATDEA }
10331     { EBCDIC-CA-FR csEBCDICCAFR }
10332     { EBCDIC-DK-NO csEBCDICDKNO }
10333     { EBCDIC-DK-NO-A csEBCDICDKNOA }
10334     { EBCDIC-FI-SE csEBCDICFISE }
10335     { EBCDIC-FI-SE-A csEBCDICFISEA }
10336     { EBCDIC-FR csEBCDICFR }
10337     { EBCDIC-IT csEBCDICIT }
10338     { EBCDIC-PT csEBCDICPT }
10339     { EBCDIC-ES csEBCDICES }
10340     { EBCDIC-ES-A csEBCDICESA }
10341     { EBCDIC-ES-S csEBCDICESS }
10342     { EBCDIC-UK csEBCDICUK }
10343     { EBCDIC-US csEBCDICUS }
10344     { UNKNOWN-8BIT csUnknown8BiT }
10345     { MNEMONIC csMnemonic }
10346     { MNEM csMnem }
10347     { VISCII csVISCII }
10348     { VIQR csVIQR }
10349     { KOI8-R csKOI8R }
10350     { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
10351     { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
10352     { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
10353     { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
10354     { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
10355     { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
10356     { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
10357     { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
10358     { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
10359     { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
10360     { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
10361     { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
10362     { IBM1047 IBM-1047 }
10363     { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
10364     { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
10365     { UNICODE-1-1 csUnicode11 }
10366     { CESU-8 csCESU-8 }
10367     { BOCU-1 csBOCU-1 }
10368     { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
10369     { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
10370       l8 }
10371     { ISO-8859-15 ISO_8859-15 Latin-9 }
10372     { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
10373     { GBK CP936 MS936 windows-936 }
10374     { JIS_Encoding csJISEncoding }
10375     { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
10376     { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
10377       EUC-JP }
10378     { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
10379     { ISO-10646-UCS-Basic csUnicodeASCII }
10380     { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
10381     { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
10382     { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
10383     { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
10384     { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
10385     { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
10386     { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
10387     { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
10388     { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
10389     { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
10390     { Adobe-Standard-Encoding csAdobeStandardEncoding }
10391     { Ventura-US csVenturaUS }
10392     { Ventura-International csVenturaInternational }
10393     { PC8-Danish-Norwegian csPC8DanishNorwegian }
10394     { PC8-Turkish csPC8Turkish }
10395     { IBM-Symbols csIBMSymbols }
10396     { IBM-Thai csIBMThai }
10397     { HP-Legal csHPLegal }
10398     { HP-Pi-font csHPPiFont }
10399     { HP-Math8 csHPMath8 }
10400     { Adobe-Symbol-Encoding csHPPSMath }
10401     { HP-DeskTop csHPDesktop }
10402     { Ventura-Math csVenturaMath }
10403     { Microsoft-Publishing csMicrosoftPublishing }
10404     { Windows-31J csWindows31J }
10405     { GB2312 csGB2312 }
10406     { Big5 csBig5 }
10407 }
10408
10409 proc tcl_encoding {enc} {
10410     global encoding_aliases tcl_encoding_cache
10411     if {[info exists tcl_encoding_cache($enc)]} {
10412         return $tcl_encoding_cache($enc)
10413     }
10414     set names [encoding names]
10415     set lcnames [string tolower $names]
10416     set enc [string tolower $enc]
10417     set i [lsearch -exact $lcnames $enc]
10418     if {$i < 0} {
10419         # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
10420         if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
10421             set i [lsearch -exact $lcnames $encx]
10422         }
10423     }
10424     if {$i < 0} {
10425         foreach l $encoding_aliases {
10426             set ll [string tolower $l]
10427             if {[lsearch -exact $ll $enc] < 0} continue
10428             # look through the aliases for one that tcl knows about
10429             foreach e $ll {
10430                 set i [lsearch -exact $lcnames $e]
10431                 if {$i < 0} {
10432                     if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
10433                         set i [lsearch -exact $lcnames $ex]
10434                     }
10435                 }
10436                 if {$i >= 0} break
10437             }
10438             break
10439         }
10440     }
10441     set tclenc {}
10442     if {$i >= 0} {
10443         set tclenc [lindex $names $i]
10444     }
10445     set tcl_encoding_cache($enc) $tclenc
10446     return $tclenc
10447 }
10448
10449 proc gitattr {path attr default} {
10450     global path_attr_cache
10451     if {[info exists path_attr_cache($attr,$path)]} {
10452         set r $path_attr_cache($attr,$path)
10453     } else {
10454         set r "unspecified"
10455         if {![catch {set line [exec git check-attr $attr -- $path]}]} {
10456             regexp "(.*): encoding: (.*)" $line m f r
10457         }
10458         set path_attr_cache($attr,$path) $r
10459     }
10460     if {$r eq "unspecified"} {
10461         return $default
10462     }
10463     return $r
10464 }
10465
10466 proc cache_gitattr {attr pathlist} {
10467     global path_attr_cache
10468     set newlist {}
10469     foreach path $pathlist {
10470         if {![info exists path_attr_cache($attr,$path)]} {
10471             lappend newlist $path
10472         }
10473     }
10474     set lim 1000
10475     if {[tk windowingsystem] == "win32"} {
10476         # windows has a 32k limit on the arguments to a command...
10477         set lim 30
10478     }
10479     while {$newlist ne {}} {
10480         set head [lrange $newlist 0 [expr {$lim - 1}]]
10481         set newlist [lrange $newlist $lim end]
10482         if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
10483             foreach row [split $rlist "\n"] {
10484                 if {[regexp "(.*): encoding: (.*)" $row m path value]} {
10485                     if {[string index $path 0] eq "\""} {
10486                         set path [encoding convertfrom [lindex $path 0]]
10487                     }
10488                     set path_attr_cache($attr,$path) $value
10489                 }
10490             }
10491         }
10492     }
10493 }
10494
10495 proc get_path_encoding {path} {
10496     global gui_encoding perfile_attrs
10497     set tcl_enc $gui_encoding
10498     if {$path ne {} && $perfile_attrs} {
10499         set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
10500         if {$enc2 ne {}} {
10501             set tcl_enc $enc2
10502         }
10503     }
10504     return $tcl_enc
10505 }
10506
10507 # First check that Tcl/Tk is recent enough
10508 if {[catch {package require Tk 8.4} err]} {
10509     show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
10510                      Gitk requires at least Tcl/Tk 8.4."]
10511     exit 1
10512 }
10513
10514 # defaults...
10515 set wrcomcmd "git diff-tree --stdin -p --pretty"
10516
10517 set gitencoding {}
10518 catch {
10519     set gitencoding [exec git config --get i18n.commitencoding]
10520 }
10521 if {$gitencoding == ""} {
10522     set gitencoding "utf-8"
10523 }
10524 set tclencoding [tcl_encoding $gitencoding]
10525 if {$tclencoding == {}} {
10526     puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
10527 }
10528
10529 set gui_encoding [encoding system]
10530 catch {
10531     set enc [exec git config --get gui.encoding]
10532     if {$enc ne {}} {
10533         set tclenc [tcl_encoding $enc]
10534         if {$tclenc ne {}} {
10535             set gui_encoding $tclenc
10536         } else {
10537             puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
10538         }
10539     }
10540 }
10541
10542 set mainfont {Helvetica 9}
10543 set textfont {Courier 9}
10544 set uifont {Helvetica 9 bold}
10545 set tabstop 8
10546 set findmergefiles 0
10547 set maxgraphpct 50
10548 set maxwidth 16
10549 set revlistorder 0
10550 set fastdate 0
10551 set uparrowlen 5
10552 set downarrowlen 5
10553 set mingaplen 100
10554 set cmitmode "patch"
10555 set wrapcomment "none"
10556 set showneartags 1
10557 set maxrefs 20
10558 set maxlinelen 200
10559 set showlocalchanges 1
10560 set limitdiffs 1
10561 set datetimeformat "%Y-%m-%d %H:%M:%S"
10562 set autoselect 1
10563 set perfile_attrs 0
10564
10565 set extdifftool "meld"
10566
10567 set colors {green red blue magenta darkgrey brown orange}
10568 set bgcolor white
10569 set fgcolor black
10570 set diffcolors {red "#00a000" blue}
10571 set diffcontext 3
10572 set ignorespace 0
10573 set selectbgcolor gray85
10574 set markbgcolor "#e0e0ff"
10575
10576 set circlecolors {white blue gray blue blue}
10577
10578 # button for popping up context menus
10579 if {[tk windowingsystem] eq "aqua"} {
10580     set ctxbut <Button-2>
10581 } else {
10582     set ctxbut <Button-3>
10583 }
10584
10585 ## For msgcat loading, first locate the installation location.
10586 if { [info exists ::env(GITK_MSGSDIR)] } {
10587     ## Msgsdir was manually set in the environment.
10588     set gitk_msgsdir $::env(GITK_MSGSDIR)
10589 } else {
10590     ## Let's guess the prefix from argv0.
10591     set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
10592     set gitk_libdir [file join $gitk_prefix share gitk lib]
10593     set gitk_msgsdir [file join $gitk_libdir msgs]
10594     unset gitk_prefix
10595 }
10596
10597 ## Internationalization (i18n) through msgcat and gettext. See
10598 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
10599 package require msgcat
10600 namespace import ::msgcat::mc
10601 ## And eventually load the actual message catalog
10602 ::msgcat::mcload $gitk_msgsdir
10603
10604 catch {source ~/.gitk}
10605
10606 font create optionfont -family sans-serif -size -12
10607
10608 parsefont mainfont $mainfont
10609 eval font create mainfont [fontflags mainfont]
10610 eval font create mainfontbold [fontflags mainfont 1]
10611
10612 parsefont textfont $textfont
10613 eval font create textfont [fontflags textfont]
10614 eval font create textfontbold [fontflags textfont 1]
10615
10616 parsefont uifont $uifont
10617 eval font create uifont [fontflags uifont]
10618
10619 setoptions
10620
10621 # check that we can find a .git directory somewhere...
10622 if {[catch {set gitdir [gitdir]}]} {
10623     show_error {} . [mc "Cannot find a git repository here."]
10624     exit 1
10625 }
10626 if {![file isdirectory $gitdir]} {
10627     show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
10628     exit 1
10629 }
10630
10631 set selecthead {}
10632 set selectheadid {}
10633
10634 set revtreeargs {}
10635 set cmdline_files {}
10636 set i 0
10637 set revtreeargscmd {}
10638 foreach arg $argv {
10639     switch -glob -- $arg {
10640         "" { }
10641         "--" {
10642             set cmdline_files [lrange $argv [expr {$i + 1}] end]
10643             break
10644         }
10645         "--select-commit=*" {
10646             set selecthead [string range $arg 16 end]
10647         }
10648         "--argscmd=*" {
10649             set revtreeargscmd [string range $arg 10 end]
10650         }
10651         default {
10652             lappend revtreeargs $arg
10653         }
10654     }
10655     incr i
10656 }
10657
10658 if {$selecthead eq "HEAD"} {
10659     set selecthead {}
10660 }
10661
10662 if {$i >= [llength $argv] && $revtreeargs ne {}} {
10663     # no -- on command line, but some arguments (other than --argscmd)
10664     if {[catch {
10665         set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
10666         set cmdline_files [split $f "\n"]
10667         set n [llength $cmdline_files]
10668         set revtreeargs [lrange $revtreeargs 0 end-$n]
10669         # Unfortunately git rev-parse doesn't produce an error when
10670         # something is both a revision and a filename.  To be consistent
10671         # with git log and git rev-list, check revtreeargs for filenames.
10672         foreach arg $revtreeargs {
10673             if {[file exists $arg]} {
10674                 show_error {} . [mc "Ambiguous argument '%s': both revision\
10675                                  and filename" $arg]
10676                 exit 1
10677             }
10678         }
10679     } err]} {
10680         # unfortunately we get both stdout and stderr in $err,
10681         # so look for "fatal:".
10682         set i [string first "fatal:" $err]
10683         if {$i > 0} {
10684             set err [string range $err [expr {$i + 6}] end]
10685         }
10686         show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
10687         exit 1
10688     }
10689 }
10690
10691 set nullid "0000000000000000000000000000000000000000"
10692 set nullid2 "0000000000000000000000000000000000000001"
10693 set nullfile "/dev/null"
10694
10695 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
10696
10697 set runq {}
10698 set history {}
10699 set historyindex 0
10700 set fh_serial 0
10701 set nhl_names {}
10702 set highlight_paths {}
10703 set findpattern {}
10704 set searchdirn -forwards
10705 set boldrows {}
10706 set boldnamerows {}
10707 set diffelide {0 0}
10708 set markingmatches 0
10709 set linkentercount 0
10710 set need_redisplay 0
10711 set nrows_drawn 0
10712 set firsttabstop 0
10713
10714 set nextviewnum 1
10715 set curview 0
10716 set selectedview 0
10717 set selectedhlview [mc "None"]
10718 set highlight_related [mc "None"]
10719 set highlight_files {}
10720 set viewfiles(0) {}
10721 set viewperm(0) 0
10722 set viewargs(0) {}
10723 set viewargscmd(0) {}
10724
10725 set selectedline {}
10726 set numcommits 0
10727 set loginstance 0
10728 set cmdlineok 0
10729 set stopped 0
10730 set stuffsaved 0
10731 set patchnum 0
10732 set lserial 0
10733 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
10734 setcoords
10735 makewindow
10736 # wait for the window to become visible
10737 tkwait visibility .
10738 wm title . "[file tail $argv0]: [file tail [pwd]]"
10739 readrefs
10740
10741 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
10742     # create a view for the files/dirs specified on the command line
10743     set curview 1
10744     set selectedview 1
10745     set nextviewnum 2
10746     set viewname(1) [mc "Command line"]
10747     set viewfiles(1) $cmdline_files
10748     set viewargs(1) $revtreeargs
10749     set viewargscmd(1) $revtreeargscmd
10750     set viewperm(1) 0
10751     set vdatemode(1) 0
10752     addviewmenu 1
10753     .bar.view entryconf [mca "Edit view..."] -state normal
10754     .bar.view entryconf [mca "Delete view"] -state normal
10755 }
10756
10757 if {[info exists permviews]} {
10758     foreach v $permviews {
10759         set n $nextviewnum
10760         incr nextviewnum
10761         set viewname($n) [lindex $v 0]
10762         set viewfiles($n) [lindex $v 1]
10763         set viewargs($n) [lindex $v 2]
10764         set viewargscmd($n) [lindex $v 3]
10765         set viewperm($n) 1
10766         addviewmenu $n
10767     }
10768 }
10769 getcommits {}