Code

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