Code

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