Code

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