Code

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