Code

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