Code

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