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