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