Code

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