Code

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