2 # Tcl ignores the next line -*- tcl -*- \
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.
12 if {[info exists env(GIT_DIR)]} {
15 return [exec git rev-parse --git-dir]
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.
28 if {[info exists isonrunq($script)]} return
32 lappend runq [list {} $script]
33 set isonrunq($script) 1
36 proc filerun {fd script} {
37 fileevent $fd readable [list filereadable $fd $script]
40 proc filereadable {fd script} {
43 fileevent $fd readable {}
47 lappend runq [list $fd $script]
53 for {set i 0} {$i < [llength $runq]} {} {
54 if {[lindex $runq $i 0] eq $fd} {
55 set runq [lreplace $runq $i $i]
65 set tstart [clock clicks -milliseconds]
67 while {[llength $runq] > 0} {
68 set fd [lindex $runq 0 0]
69 set script [lindex $runq 0 1]
70 set repeat [eval $script]
71 set t1 [clock clicks -milliseconds]
72 set t [expr {$t1 - $t0}]
73 set runq [lrange $runq 1 end]
74 if {$repeat ne {} && $repeat} {
75 if {$fd eq {} || $repeat == 2} {
76 # script returns 1 if it wants to be readded
77 # file readers return 2 if they could do more straight away
78 lappend runq [list $fd $script]
80 fileevent $fd readable [list filereadable $fd $script]
82 } elseif {$fd eq {}} {
83 unset isonrunq($script)
86 if {$t1 - $tstart >= 80} break
93 proc reg_instance {fd} {
94 global commfd leftover loginstance
96 set i [incr loginstance]
102 proc unmerged_files {files} {
105 # find the list of unmerged files
109 set fd [open "| git ls-files -u" r]
111 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
114 while {[gets $fd line] >= 0} {
115 set i [string first "\t" $line]
117 set fname [string range $line [expr {$i+1}] end]
118 if {[lsearch -exact $mlist $fname] >= 0} continue
120 if {$files eq {} || [path_filter $files $fname]} {
128 proc parseviewargs {n arglist} {
129 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
137 set origargs $arglist
141 foreach arg $arglist {
148 switch -glob -- $arg {
152 # remove from origargs in case we hit an unknown option
153 set origargs [lreplace $origargs $i $i]
156 # These request or affect diff output, which we don't want.
157 # Some could be used to set our defaults for diff display.
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 lappend diffargs $arg
166 # These cause our parsing of git log's output to fail, or else
167 # they're options we want to set ourselves, so ignore them.
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" {
176 # These are harmless, and some are even useful
177 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
178 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
179 "--full-history" - "--dense" - "--sparse" -
180 "--follow" - "--left-right" - "--encoding=*" {
183 # These mean that we get a subset of the commits
184 "--diff-filter=*" - "--no-merges" - "--unpacked" -
185 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
186 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
187 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
188 "--remove-empty" - "--first-parent" - "--cherry-pick" -
189 "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
193 # This appears to be the only one that has a value as a
194 # separate word following it
201 set notflag [expr {!$notflag}]
209 # git rev-parse doesn't understand --merge
210 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
212 # Other flag arguments including -<n>
214 if {[string is digit -strict [string range $arg 1 end]]} {
217 # a flag argument that we don't recognize;
218 # that means we can't optimize
223 # Non-flag arguments specify commits or ranges of commits
225 if {[string match "*...*" $arg]} {
226 lappend revargs --gitk-symmetric-diff-marker
232 set vdflags($n) $diffargs
233 set vflags($n) $glflags
234 set vrevs($n) $revargs
235 set vfiltered($n) $filtered
236 set vorigargs($n) $origargs
240 proc parseviewrevs {view revs} {
241 global vposids vnegids
246 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
247 # we get stdout followed by stderr in $err
248 # for an unknown rev, git rev-parse echoes it and then errors out
249 set errlines [split $err "\n"]
251 for {set l 0} {$l < [llength $errlines]} {incr l} {
252 set line [lindex $errlines $l]
253 if {!([string length $line] == 40 && [string is xdigit $line])} {
254 if {[string match "fatal:*" $line]} {
255 if {[string match "fatal: ambiguous argument*" $line]
257 if {[llength $badrev] == 1} {
258 set err "unknown revision $badrev"
260 set err "unknown revisions: [join $badrev ", "]"
263 set err [join [lrange $errlines $l end] "\n"]
270 error_popup "Error parsing revisions: $err"
277 foreach id [split $ids "\n"] {
278 if {$id eq "--gitk-symmetric-diff-marker"} {
280 } elseif {[string match "^*" $id]} {
287 lappend neg [string range $id 1 end]
292 lset ret end [lindex $ret end]...$id
298 set vposids($view) $pos
299 set vnegids($view) $neg
303 # Start off a git log process and arrange to read its output
304 proc start_rev_list {view} {
305 global startmsecs commitidx viewcomplete curview
307 global viewargs viewargscmd viewfiles vfilelimit
308 global showlocalchanges commitinterest
309 global viewactive viewinstances vmergeonly
310 global pending_select mainheadid
311 global vcanopt vflags vrevs vorigargs
313 set startmsecs [clock clicks -milliseconds]
314 set commitidx($view) 0
315 # these are set this way for the error exits
316 set viewcomplete($view) 1
317 set viewactive($view) 0
320 set args $viewargs($view)
321 if {$viewargscmd($view) ne {}} {
323 set str [exec sh -c $viewargscmd($view)]
325 error_popup "Error executing --argscmd command: $err"
328 set args [concat $args [split $str "\n"]]
330 set vcanopt($view) [parseviewargs $view $args]
332 set files $viewfiles($view)
333 if {$vmergeonly($view)} {
334 set files [unmerged_files $files]
337 if {$nr_unmerged == 0} {
338 error_popup [mc "No files selected: --merge specified but\
339 no files are unmerged."]
341 error_popup [mc "No files selected: --merge specified but\
342 no unmerged files are within file limit."]
347 set vfilelimit($view) $files
349 if {$vcanopt($view)} {
350 set revs [parseviewrevs $view $vrevs($view)]
354 set args [concat $vflags($view) $revs]
356 set args $vorigargs($view)
360 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
361 --boundary $args "--" $files] r]
363 error_popup "[mc "Error executing git log:"] $err"
366 set i [reg_instance $fd]
367 set viewinstances($view) [list $i]
368 if {$showlocalchanges && $mainheadid ne {}} {
369 lappend commitinterest($mainheadid) {dodiffindex}
371 fconfigure $fd -blocking 0 -translation lf -eofchar {}
372 if {$tclencoding != {}} {
373 fconfigure $fd -encoding $tclencoding
375 filerun $fd [list getcommitlines $fd $i $view 0]
376 nowbusy $view [mc "Reading"]
377 if {$view == $curview} {
378 set pending_select $mainheadid
380 set viewcomplete($view) 0
381 set viewactive($view) 1
385 proc stop_instance {inst} {
386 global commfd leftover
388 set fd $commfd($inst)
396 unset leftover($inst)
399 proc stop_backends {} {
402 foreach inst [array names commfd] {
407 proc stop_rev_list {view} {
410 foreach inst $viewinstances($view) {
413 set viewinstances($view) {}
417 global canv curview need_redisplay viewactive
420 if {[start_rev_list $curview]} {
421 show_status [mc "Reading commits..."]
424 show_status [mc "No commits selected"]
428 proc updatecommits {} {
429 global curview vcanopt vorigargs vfilelimit viewinstances
430 global viewactive viewcomplete tclencoding
431 global startmsecs showneartags showlocalchanges
432 global mainheadid pending_select
434 global varcid vposids vnegids vflags vrevs
436 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
437 set oldmainid $mainheadid
439 if {$showlocalchanges} {
440 if {$mainheadid ne $oldmainid} {
443 if {[commitinview $mainheadid $curview]} {
448 if {$vcanopt($view)} {
449 set oldpos $vposids($view)
450 set oldneg $vnegids($view)
451 set revs [parseviewrevs $view $vrevs($view)]
455 # note: getting the delta when negative refs change is hard,
456 # and could require multiple git log invocations, so in that
457 # case we ask git log for all the commits (not just the delta)
458 if {$oldneg eq $vnegids($view)} {
461 # take out positive refs that we asked for before or
462 # that we have already seen
464 if {[string length $rev] == 40} {
465 if {[lsearch -exact $oldpos $rev] < 0
466 && ![info exists varcid($view,$rev)]} {
471 lappend $newrevs $rev
474 if {$npos == 0} return
476 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
478 set args [concat $vflags($view) $revs --not $oldpos]
480 set args $vorigargs($view)
483 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
484 --boundary $args "--" $vfilelimit($view)] r]
486 error_popup "Error executing git log: $err"
489 if {$viewactive($view) == 0} {
490 set startmsecs [clock clicks -milliseconds]
492 set i [reg_instance $fd]
493 lappend viewinstances($view) $i
494 fconfigure $fd -blocking 0 -translation lf -eofchar {}
495 if {$tclencoding != {}} {
496 fconfigure $fd -encoding $tclencoding
498 filerun $fd [list getcommitlines $fd $i $view 1]
499 incr viewactive($view)
500 set viewcomplete($view) 0
501 set pending_select $mainheadid
502 nowbusy $view "Reading"
508 proc reloadcommits {} {
509 global curview viewcomplete selectedline currentid thickerline
510 global showneartags treediffs commitinterest cached_commitrow
513 if {!$viewcomplete($curview)} {
514 stop_rev_list $curview
518 catch {unset currentid}
519 catch {unset thickerline}
520 catch {unset treediffs}
527 catch {unset commitinterest}
528 catch {unset cached_commitrow}
529 catch {unset targetid}
535 # This makes a string representation of a positive integer which
536 # sorts as a string in numerical order
539 return [format "%x" $n]
540 } elseif {$n < 256} {
541 return [format "x%.2x" $n]
542 } elseif {$n < 65536} {
543 return [format "y%.4x" $n]
545 return [format "z%.8x" $n]
548 # Procedures used in reordering commits from git log (without
549 # --topo-order) into the order for display.
551 proc varcinit {view} {
552 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
553 global vtokmod varcmod vrowmod varcix vlastins
555 set varcstart($view) {{}}
556 set vupptr($view) {0}
557 set vdownptr($view) {0}
558 set vleftptr($view) {0}
559 set vbackptr($view) {0}
560 set varctok($view) {{}}
561 set varcrow($view) {{}}
562 set vtokmod($view) {}
565 set varcix($view) {{}}
566 set vlastins($view) {0}
569 proc resetvarcs {view} {
570 global varcid varccommits parents children vseedcount ordertok
572 foreach vid [array names varcid $view,*] {
577 # some commits might have children but haven't been seen yet
578 foreach vid [array names children $view,*] {
581 foreach va [array names varccommits $view,*] {
582 unset varccommits($va)
584 foreach vd [array names vseedcount $view,*] {
585 unset vseedcount($vd)
587 catch {unset ordertok}
590 # returns a list of the commits with no children
592 global vdownptr vleftptr varcstart
595 set a [lindex $vdownptr($v) 0]
597 lappend ret [lindex $varcstart($v) $a]
598 set a [lindex $vleftptr($v) $a]
603 proc newvarc {view id} {
604 global varcid varctok parents children vdatemode
605 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
606 global commitdata commitinfo vseedcount varccommits vlastins
608 set a [llength $varctok($view)]
610 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
611 if {![info exists commitinfo($id)]} {
612 parsecommit $id $commitdata($id) 1
614 set cdate [lindex $commitinfo($id) 4]
615 if {![string is integer -strict $cdate]} {
618 if {![info exists vseedcount($view,$cdate)]} {
619 set vseedcount($view,$cdate) -1
621 set c [incr vseedcount($view,$cdate)]
622 set cdate [expr {$cdate ^ 0xffffffff}]
623 set tok "s[strrep $cdate][strrep $c]"
628 if {[llength $children($vid)] > 0} {
629 set kid [lindex $children($vid) end]
630 set k $varcid($view,$kid)
631 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
634 set tok [lindex $varctok($view) $k]
638 set i [lsearch -exact $parents($view,$ki) $id]
639 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
640 append tok [strrep $j]
642 set c [lindex $vlastins($view) $ka]
643 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
645 set b [lindex $vdownptr($view) $ka]
647 set b [lindex $vleftptr($view) $c]
649 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
651 set b [lindex $vleftptr($view) $c]
654 lset vdownptr($view) $ka $a
655 lappend vbackptr($view) 0
657 lset vleftptr($view) $c $a
658 lappend vbackptr($view) $c
660 lset vlastins($view) $ka $a
661 lappend vupptr($view) $ka
662 lappend vleftptr($view) $b
664 lset vbackptr($view) $b $a
666 lappend varctok($view) $tok
667 lappend varcstart($view) $id
668 lappend vdownptr($view) 0
669 lappend varcrow($view) {}
670 lappend varcix($view) {}
671 set varccommits($view,$a) {}
672 lappend vlastins($view) 0
676 proc splitvarc {p v} {
677 global varcid varcstart varccommits varctok
678 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
680 set oa $varcid($v,$p)
681 set ac $varccommits($v,$oa)
682 set i [lsearch -exact $varccommits($v,$oa) $p]
684 set na [llength $varctok($v)]
685 # "%" sorts before "0"...
686 set tok "[lindex $varctok($v) $oa]%[strrep $i]"
687 lappend varctok($v) $tok
688 lappend varcrow($v) {}
689 lappend varcix($v) {}
690 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
691 set varccommits($v,$na) [lrange $ac $i end]
692 lappend varcstart($v) $p
693 foreach id $varccommits($v,$na) {
694 set varcid($v,$id) $na
696 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
697 lappend vlastins($v) [lindex $vlastins($v) $oa]
698 lset vdownptr($v) $oa $na
699 lset vlastins($v) $oa 0
700 lappend vupptr($v) $oa
701 lappend vleftptr($v) 0
702 lappend vbackptr($v) 0
703 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
704 lset vupptr($v) $b $na
708 proc renumbervarc {a v} {
709 global parents children varctok varcstart varccommits
710 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
712 set t1 [clock clicks -milliseconds]
718 if {[info exists isrelated($a)]} {
720 set id [lindex $varccommits($v,$a) end]
721 foreach p $parents($v,$id) {
722 if {[info exists varcid($v,$p)]} {
723 set isrelated($varcid($v,$p)) 1
728 set b [lindex $vdownptr($v) $a]
731 set b [lindex $vleftptr($v) $a]
733 set a [lindex $vupptr($v) $a]
739 if {![info exists kidchanged($a)]} continue
740 set id [lindex $varcstart($v) $a]
741 if {[llength $children($v,$id)] > 1} {
742 set children($v,$id) [lsort -command [list vtokcmp $v] \
745 set oldtok [lindex $varctok($v) $a]
746 if {!$vdatemode($v)} {
752 set kid [last_real_child $v,$id]
754 set k $varcid($v,$kid)
755 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
758 set tok [lindex $varctok($v) $k]
762 set i [lsearch -exact $parents($v,$ki) $id]
763 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
764 append tok [strrep $j]
766 if {$tok eq $oldtok} {
769 set id [lindex $varccommits($v,$a) end]
770 foreach p $parents($v,$id) {
771 if {[info exists varcid($v,$p)]} {
772 set kidchanged($varcid($v,$p)) 1
777 lset varctok($v) $a $tok
778 set b [lindex $vupptr($v) $a]
780 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
783 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
786 set c [lindex $vbackptr($v) $a]
787 set d [lindex $vleftptr($v) $a]
789 lset vdownptr($v) $b $d
791 lset vleftptr($v) $c $d
794 lset vbackptr($v) $d $c
796 if {[lindex $vlastins($v) $b] == $a} {
797 lset vlastins($v) $b $c
799 lset vupptr($v) $a $ka
800 set c [lindex $vlastins($v) $ka]
802 [string compare $tok [lindex $varctok($v) $c]] < 0} {
804 set b [lindex $vdownptr($v) $ka]
806 set b [lindex $vleftptr($v) $c]
809 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
811 set b [lindex $vleftptr($v) $c]
814 lset vdownptr($v) $ka $a
815 lset vbackptr($v) $a 0
817 lset vleftptr($v) $c $a
818 lset vbackptr($v) $a $c
820 lset vleftptr($v) $a $b
822 lset vbackptr($v) $b $a
824 lset vlastins($v) $ka $a
827 foreach id [array names sortkids] {
828 if {[llength $children($v,$id)] > 1} {
829 set children($v,$id) [lsort -command [list vtokcmp $v] \
833 set t2 [clock clicks -milliseconds]
834 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
837 # Fix up the graph after we have found out that in view $v,
838 # $p (a commit that we have already seen) is actually the parent
839 # of the last commit in arc $a.
840 proc fix_reversal {p a v} {
841 global varcid varcstart varctok vupptr
843 set pa $varcid($v,$p)
844 if {$p ne [lindex $varcstart($v) $pa]} {
846 set pa $varcid($v,$p)
848 # seeds always need to be renumbered
849 if {[lindex $vupptr($v) $pa] == 0 ||
850 [string compare [lindex $varctok($v) $a] \
851 [lindex $varctok($v) $pa]] > 0} {
856 proc insertrow {id p v} {
857 global cmitlisted children parents varcid varctok vtokmod
858 global varccommits ordertok commitidx numcommits curview
859 global targetid targetrow
863 set cmitlisted($vid) 1
864 set children($vid) {}
865 set parents($vid) [list $p]
866 set a [newvarc $v $id]
868 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
871 lappend varccommits($v,$a) $id
873 if {[llength [lappend children($vp) $id]] > 1} {
874 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
875 catch {unset ordertok}
877 fix_reversal $p $a $v
879 if {$v == $curview} {
880 set numcommits $commitidx($v)
882 if {[info exists targetid]} {
883 if {![comes_before $targetid $p]} {
890 proc insertfakerow {id p} {
891 global varcid varccommits parents children cmitlisted
892 global commitidx varctok vtokmod targetid targetrow curview numcommits
896 set i [lsearch -exact $varccommits($v,$a) $p]
898 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
901 set children($v,$id) {}
902 set parents($v,$id) [list $p]
903 set varcid($v,$id) $a
904 lappend children($v,$p) $id
905 set cmitlisted($v,$id) 1
906 set numcommits [incr commitidx($v)]
907 # note we deliberately don't update varcstart($v) even if $i == 0
908 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
910 if {[info exists targetid]} {
911 if {![comes_before $targetid $p]} {
919 proc removefakerow {id} {
920 global varcid varccommits parents children commitidx
921 global varctok vtokmod cmitlisted currentid selectedline
922 global targetid curview numcommits
925 if {[llength $parents($v,$id)] != 1} {
926 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
929 set p [lindex $parents($v,$id) 0]
930 set a $varcid($v,$id)
931 set i [lsearch -exact $varccommits($v,$a) $id]
933 puts "oops: removefakerow can't find [shortids $id] on arc $a"
937 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
938 unset parents($v,$id)
939 unset children($v,$id)
940 unset cmitlisted($v,$id)
941 set numcommits [incr commitidx($v) -1]
942 set j [lsearch -exact $children($v,$p) $id]
944 set children($v,$p) [lreplace $children($v,$p) $j $j]
947 if {[info exist currentid] && $id eq $currentid} {
951 if {[info exists targetid] && $targetid eq $id} {
958 proc first_real_child {vp} {
959 global children nullid nullid2
961 foreach id $children($vp) {
962 if {$id ne $nullid && $id ne $nullid2} {
969 proc last_real_child {vp} {
970 global children nullid nullid2
972 set kids $children($vp)
973 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
974 set id [lindex $kids $i]
975 if {$id ne $nullid && $id ne $nullid2} {
982 proc vtokcmp {v a b} {
983 global varctok varcid
985 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
986 [lindex $varctok($v) $varcid($v,$b)]]
989 # This assumes that if lim is not given, the caller has checked that
990 # arc a's token is less than $vtokmod($v)
991 proc modify_arc {v a {lim {}}} {
992 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
995 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
998 set r [lindex $varcrow($v) $a]
999 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1002 set vtokmod($v) [lindex $varctok($v) $a]
1004 if {$v == $curview} {
1005 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1006 set a [lindex $vupptr($v) $a]
1012 set lim [llength $varccommits($v,$a)]
1014 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1021 proc update_arcrows {v} {
1022 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1023 global varcid vrownum varcorder varcix varccommits
1024 global vupptr vdownptr vleftptr varctok
1025 global displayorder parentlist curview cached_commitrow
1027 if {$vrowmod($v) == $commitidx($v)} return
1028 if {$v == $curview} {
1029 if {[llength $displayorder] > $vrowmod($v)} {
1030 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1031 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1033 catch {unset cached_commitrow}
1035 set narctot [expr {[llength $varctok($v)] - 1}]
1037 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1038 # go up the tree until we find something that has a row number,
1039 # or we get to a seed
1040 set a [lindex $vupptr($v) $a]
1043 set a [lindex $vdownptr($v) 0]
1046 set varcorder($v) [list $a]
1047 lset varcix($v) $a 0
1048 lset varcrow($v) $a 0
1052 set arcn [lindex $varcix($v) $a]
1053 if {[llength $vrownum($v)] > $arcn + 1} {
1054 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1055 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1057 set row [lindex $varcrow($v) $a]
1061 incr row [llength $varccommits($v,$a)]
1062 # go down if possible
1063 set b [lindex $vdownptr($v) $a]
1065 # if not, go left, or go up until we can go left
1067 set b [lindex $vleftptr($v) $a]
1069 set a [lindex $vupptr($v) $a]
1075 lappend vrownum($v) $row
1076 lappend varcorder($v) $a
1077 lset varcix($v) $a $arcn
1078 lset varcrow($v) $a $row
1080 set vtokmod($v) [lindex $varctok($v) $p]
1082 set vrowmod($v) $row
1083 if {[info exists currentid]} {
1084 set selectedline [rowofcommit $currentid]
1088 # Test whether view $v contains commit $id
1089 proc commitinview {id v} {
1092 return [info exists varcid($v,$id)]
1095 # Return the row number for commit $id in the current view
1096 proc rowofcommit {id} {
1097 global varcid varccommits varcrow curview cached_commitrow
1098 global varctok vtokmod
1101 if {![info exists varcid($v,$id)]} {
1102 puts "oops rowofcommit no arc for [shortids $id]"
1105 set a $varcid($v,$id)
1106 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1109 if {[info exists cached_commitrow($id)]} {
1110 return $cached_commitrow($id)
1112 set i [lsearch -exact $varccommits($v,$a) $id]
1114 puts "oops didn't find commit [shortids $id] in arc $a"
1117 incr i [lindex $varcrow($v) $a]
1118 set cached_commitrow($id) $i
1122 # Returns 1 if a is on an earlier row than b, otherwise 0
1123 proc comes_before {a b} {
1124 global varcid varctok curview
1127 if {$a eq $b || ![info exists varcid($v,$a)] || \
1128 ![info exists varcid($v,$b)]} {
1131 if {$varcid($v,$a) != $varcid($v,$b)} {
1132 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1133 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1135 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1138 proc bsearch {l elt} {
1139 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1144 while {$hi - $lo > 1} {
1145 set mid [expr {int(($lo + $hi) / 2)}]
1146 set t [lindex $l $mid]
1149 } elseif {$elt > $t} {
1158 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1159 proc make_disporder {start end} {
1160 global vrownum curview commitidx displayorder parentlist
1161 global varccommits varcorder parents vrowmod varcrow
1162 global d_valid_start d_valid_end
1164 if {$end > $vrowmod($curview)} {
1165 update_arcrows $curview
1167 set ai [bsearch $vrownum($curview) $start]
1168 set start [lindex $vrownum($curview) $ai]
1169 set narc [llength $vrownum($curview)]
1170 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1171 set a [lindex $varcorder($curview) $ai]
1172 set l [llength $displayorder]
1173 set al [llength $varccommits($curview,$a)]
1174 if {$l < $r + $al} {
1176 set pad [ntimes [expr {$r - $l}] {}]
1177 set displayorder [concat $displayorder $pad]
1178 set parentlist [concat $parentlist $pad]
1179 } elseif {$l > $r} {
1180 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1181 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1183 foreach id $varccommits($curview,$a) {
1184 lappend displayorder $id
1185 lappend parentlist $parents($curview,$id)
1187 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1189 foreach id $varccommits($curview,$a) {
1190 lset displayorder $i $id
1191 lset parentlist $i $parents($curview,$id)
1199 proc commitonrow {row} {
1202 set id [lindex $displayorder $row]
1204 make_disporder $row [expr {$row + 1}]
1205 set id [lindex $displayorder $row]
1210 proc closevarcs {v} {
1211 global varctok varccommits varcid parents children
1212 global cmitlisted commitidx commitinterest vtokmod
1214 set missing_parents 0
1216 set narcs [llength $varctok($v)]
1217 for {set a 1} {$a < $narcs} {incr a} {
1218 set id [lindex $varccommits($v,$a) end]
1219 foreach p $parents($v,$id) {
1220 if {[info exists varcid($v,$p)]} continue
1221 # add p as a new commit
1222 incr missing_parents
1223 set cmitlisted($v,$p) 0
1224 set parents($v,$p) {}
1225 if {[llength $children($v,$p)] == 1 &&
1226 [llength $parents($v,$id)] == 1} {
1229 set b [newvarc $v $p]
1231 set varcid($v,$p) $b
1232 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1235 lappend varccommits($v,$b) $p
1237 if {[info exists commitinterest($p)]} {
1238 foreach script $commitinterest($p) {
1239 lappend scripts [string map [list "%I" $p] $script]
1241 unset commitinterest($id)
1245 if {$missing_parents > 0} {
1246 foreach s $scripts {
1252 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1253 # Assumes we already have an arc for $rwid.
1254 proc rewrite_commit {v id rwid} {
1255 global children parents varcid varctok vtokmod varccommits
1257 foreach ch $children($v,$id) {
1258 # make $rwid be $ch's parent in place of $id
1259 set i [lsearch -exact $parents($v,$ch) $id]
1261 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1263 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1264 # add $ch to $rwid's children and sort the list if necessary
1265 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1266 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1267 $children($v,$rwid)]
1269 # fix the graph after joining $id to $rwid
1270 set a $varcid($v,$ch)
1271 fix_reversal $rwid $a $v
1272 # parentlist is wrong for the last element of arc $a
1273 # even if displayorder is right, hence the 3rd arg here
1274 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1278 proc getcommitlines {fd inst view updating} {
1279 global cmitlisted commitinterest leftover
1280 global commitidx commitdata vdatemode
1281 global parents children curview hlview
1282 global idpending ordertok
1283 global varccommits varcid varctok vtokmod vfilelimit
1285 set stuff [read $fd 500000]
1286 # git log doesn't terminate the last commit with a null...
1287 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1294 global commfd viewcomplete viewactive viewname
1295 global viewinstances
1297 set i [lsearch -exact $viewinstances($view) $inst]
1299 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1301 # set it blocking so we wait for the process to terminate
1302 fconfigure $fd -blocking 1
1303 if {[catch {close $fd} err]} {
1305 if {$view != $curview} {
1306 set fv " for the \"$viewname($view)\" view"
1308 if {[string range $err 0 4] == "usage"} {
1309 set err "Gitk: error reading commits$fv:\
1310 bad arguments to git log."
1311 if {$viewname($view) eq "Command line"} {
1313 " (Note: arguments to gitk are passed to git log\
1314 to allow selection of commits to be displayed.)"
1317 set err "Error reading commits$fv: $err"
1321 if {[incr viewactive($view) -1] <= 0} {
1322 set viewcomplete($view) 1
1323 # Check if we have seen any ids listed as parents that haven't
1324 # appeared in the list
1328 if {$view == $curview} {
1337 set i [string first "\0" $stuff $start]
1339 append leftover($inst) [string range $stuff $start end]
1343 set cmit $leftover($inst)
1344 append cmit [string range $stuff 0 [expr {$i - 1}]]
1345 set leftover($inst) {}
1347 set cmit [string range $stuff $start [expr {$i - 1}]]
1349 set start [expr {$i + 1}]
1350 set j [string first "\n" $cmit]
1353 if {$j >= 0 && [string match "commit *" $cmit]} {
1354 set ids [string range $cmit 7 [expr {$j - 1}]]
1355 if {[string match {[-^<>]*} $ids]} {
1356 switch -- [string index $ids 0] {
1362 set ids [string range $ids 1 end]
1366 if {[string length $id] != 40} {
1374 if {[string length $shortcmit] > 80} {
1375 set shortcmit "[string range $shortcmit 0 80]..."
1377 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1380 set id [lindex $ids 0]
1383 if {!$listed && $updating && ![info exists varcid($vid)] &&
1384 $vfilelimit($view) ne {}} {
1385 # git log doesn't rewrite parents for unlisted commits
1386 # when doing path limiting, so work around that here
1387 # by working out the rewritten parent with git rev-list
1388 # and if we already know about it, using the rewritten
1389 # parent as a substitute parent for $id's children.
1391 set rwid [exec git rev-list --first-parent --max-count=1 \
1392 $id -- $vfilelimit($view)]
1394 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1395 # use $rwid in place of $id
1396 rewrite_commit $view $id $rwid
1403 if {[info exists varcid($vid)]} {
1404 if {$cmitlisted($vid) || !$listed} continue
1408 set olds [lrange $ids 1 end]
1412 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1413 set cmitlisted($vid) $listed
1414 set parents($vid) $olds
1415 if {![info exists children($vid)]} {
1416 set children($vid) {}
1417 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1418 set k [lindex $children($vid) 0]
1419 if {[llength $parents($view,$k)] == 1 &&
1420 (!$vdatemode($view) ||
1421 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1422 set a $varcid($view,$k)
1427 set a [newvarc $view $id]
1429 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1432 if {![info exists varcid($vid)]} {
1434 lappend varccommits($view,$a) $id
1435 incr commitidx($view)
1440 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1442 if {[llength [lappend children($vp) $id]] > 1 &&
1443 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1444 set children($vp) [lsort -command [list vtokcmp $view] \
1446 catch {unset ordertok}
1448 if {[info exists varcid($view,$p)]} {
1449 fix_reversal $p $a $view
1455 if {[info exists commitinterest($id)]} {
1456 foreach script $commitinterest($id) {
1457 lappend scripts [string map [list "%I" $id] $script]
1459 unset commitinterest($id)
1464 global numcommits hlview
1466 if {$view == $curview} {
1467 set numcommits $commitidx($view)
1470 if {[info exists hlview] && $view == $hlview} {
1471 # we never actually get here...
1474 foreach s $scripts {
1481 proc chewcommits {} {
1482 global curview hlview viewcomplete
1483 global pending_select
1486 if {$viewcomplete($curview)} {
1487 global commitidx varctok
1488 global numcommits startmsecs
1490 if {[info exists pending_select]} {
1491 set row [first_real_row]
1494 if {$commitidx($curview) > 0} {
1495 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1496 #puts "overall $ms ms for $numcommits commits"
1497 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1499 show_status [mc "No commits selected"]
1506 proc readcommit {id} {
1507 if {[catch {set contents [exec git cat-file commit $id]}]} return
1508 parsecommit $id $contents 0
1511 proc parsecommit {id contents listed} {
1512 global commitinfo cdate
1521 set hdrend [string first "\n\n" $contents]
1523 # should never happen...
1524 set hdrend [string length $contents]
1526 set header [string range $contents 0 [expr {$hdrend - 1}]]
1527 set comment [string range $contents [expr {$hdrend + 2}] end]
1528 foreach line [split $header "\n"] {
1529 set tag [lindex $line 0]
1530 if {$tag == "author"} {
1531 set audate [lindex $line end-1]
1532 set auname [lrange $line 1 end-2]
1533 } elseif {$tag == "committer"} {
1534 set comdate [lindex $line end-1]
1535 set comname [lrange $line 1 end-2]
1539 # take the first non-blank line of the comment as the headline
1540 set headline [string trimleft $comment]
1541 set i [string first "\n" $headline]
1543 set headline [string range $headline 0 $i]
1545 set headline [string trimright $headline]
1546 set i [string first "\r" $headline]
1548 set headline [string trimright [string range $headline 0 $i]]
1551 # git log indents the comment by 4 spaces;
1552 # if we got this via git cat-file, add the indentation
1554 foreach line [split $comment "\n"] {
1555 append newcomment " "
1556 append newcomment $line
1557 append newcomment "\n"
1559 set comment $newcomment
1561 if {$comdate != {}} {
1562 set cdate($id) $comdate
1564 set commitinfo($id) [list $headline $auname $audate \
1565 $comname $comdate $comment]
1568 proc getcommit {id} {
1569 global commitdata commitinfo
1571 if {[info exists commitdata($id)]} {
1572 parsecommit $id $commitdata($id) 1
1575 if {![info exists commitinfo($id)]} {
1576 set commitinfo($id) [list [mc "No commit information available"]]
1583 global tagids idtags headids idheads tagobjid
1584 global otherrefids idotherrefs mainhead mainheadid
1586 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1589 set refd [open [list | git show-ref -d] r]
1590 while {[gets $refd line] >= 0} {
1591 if {[string index $line 40] ne " "} continue
1592 set id [string range $line 0 39]
1593 set ref [string range $line 41 end]
1594 if {![string match "refs/*" $ref]} continue
1595 set name [string range $ref 5 end]
1596 if {[string match "remotes/*" $name]} {
1597 if {![string match "*/HEAD" $name]} {
1598 set headids($name) $id
1599 lappend idheads($id) $name
1601 } elseif {[string match "heads/*" $name]} {
1602 set name [string range $name 6 end]
1603 set headids($name) $id
1604 lappend idheads($id) $name
1605 } elseif {[string match "tags/*" $name]} {
1606 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1607 # which is what we want since the former is the commit ID
1608 set name [string range $name 5 end]
1609 if {[string match "*^{}" $name]} {
1610 set name [string range $name 0 end-3]
1612 set tagobjid($name) $id
1614 set tagids($name) $id
1615 lappend idtags($id) $name
1617 set otherrefids($name) $id
1618 lappend idotherrefs($id) $name
1625 set mainheadid [exec git rev-parse HEAD]
1626 set thehead [exec git symbolic-ref HEAD]
1627 if {[string match "refs/heads/*" $thehead]} {
1628 set mainhead [string range $thehead 11 end]
1633 # skip over fake commits
1634 proc first_real_row {} {
1635 global nullid nullid2 numcommits
1637 for {set row 0} {$row < $numcommits} {incr row} {
1638 set id [commitonrow $row]
1639 if {$id ne $nullid && $id ne $nullid2} {
1646 # update things for a head moved to a child of its previous location
1647 proc movehead {id name} {
1648 global headids idheads
1650 removehead $headids($name) $name
1651 set headids($name) $id
1652 lappend idheads($id) $name
1655 # update things when a head has been removed
1656 proc removehead {id name} {
1657 global headids idheads
1659 if {$idheads($id) eq $name} {
1662 set i [lsearch -exact $idheads($id) $name]
1664 set idheads($id) [lreplace $idheads($id) $i $i]
1667 unset headids($name)
1670 proc show_error {w top msg} {
1671 message $w.m -text $msg -justify center -aspect 400
1672 pack $w.m -side top -fill x -padx 20 -pady 20
1673 button $w.ok -text [mc OK] -command "destroy $top"
1674 pack $w.ok -side bottom -fill x
1675 bind $top <Visibility> "grab $top; focus $top"
1676 bind $top <Key-Return> "destroy $top"
1680 proc error_popup msg {
1684 show_error $w $w $msg
1687 proc confirm_popup msg {
1693 message $w.m -text $msg -justify center -aspect 400
1694 pack $w.m -side top -fill x -padx 20 -pady 20
1695 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1696 pack $w.ok -side left -fill x
1697 button $w.cancel -text [mc Cancel] -command "destroy $w"
1698 pack $w.cancel -side right -fill x
1699 bind $w <Visibility> "grab $w; focus $w"
1704 proc setoptions {} {
1705 option add *Panedwindow.showHandle 1 startupFile
1706 option add *Panedwindow.sashRelief raised startupFile
1707 option add *Button.font uifont startupFile
1708 option add *Checkbutton.font uifont startupFile
1709 option add *Radiobutton.font uifont startupFile
1710 option add *Menu.font uifont startupFile
1711 option add *Menubutton.font uifont startupFile
1712 option add *Label.font uifont startupFile
1713 option add *Message.font uifont startupFile
1714 option add *Entry.font uifont startupFile
1717 proc makewindow {} {
1718 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1720 global findtype findtypemenu findloc findstring fstring geometry
1721 global entries sha1entry sha1string sha1but
1722 global diffcontextstring diffcontext
1724 global maincursor textcursor curtextcursor
1725 global rowctxmenu fakerowmenu mergemax wrapcomment
1726 global highlight_files gdttype
1727 global searchstring sstring
1728 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1729 global headctxmenu progresscanv progressitem progresscoords statusw
1730 global fprogitem fprogcoord lastprogupdate progupdatepending
1731 global rprogitem rprogcoord rownumsel numcommits
1735 .bar add cascade -label [mc "File"] -menu .bar.file
1737 .bar.file add command -label [mc "Update"] -command updatecommits
1738 .bar.file add command -label [mc "Reload"] -command reloadcommits
1739 .bar.file add command -label [mc "Reread references"] -command rereadrefs
1740 .bar.file add command -label [mc "List references"] -command showrefs
1741 .bar.file add command -label [mc "Quit"] -command doquit
1743 .bar add cascade -label [mc "Edit"] -menu .bar.edit
1744 .bar.edit add command -label [mc "Preferences"] -command doprefs
1747 .bar add cascade -label [mc "View"] -menu .bar.view
1748 .bar.view add command -label [mc "New view..."] -command {newview 0}
1749 .bar.view add command -label [mc "Edit view..."] -command editview \
1751 .bar.view add command -label [mc "Delete view"] -command delview -state disabled
1752 .bar.view add separator
1753 .bar.view add radiobutton -label [mc "All files"] -command {showview 0} \
1754 -variable selectedview -value 0
1757 .bar add cascade -label [mc "Help"] -menu .bar.help
1758 .bar.help add command -label [mc "About gitk"] -command about
1759 .bar.help add command -label [mc "Key bindings"] -command keys
1761 . configure -menu .bar
1763 # the gui has upper and lower half, parts of a paned window.
1764 panedwindow .ctop -orient vertical
1766 # possibly use assumed geometry
1767 if {![info exists geometry(pwsash0)]} {
1768 set geometry(topheight) [expr {15 * $linespc}]
1769 set geometry(topwidth) [expr {80 * $charspc}]
1770 set geometry(botheight) [expr {15 * $linespc}]
1771 set geometry(botwidth) [expr {50 * $charspc}]
1772 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1773 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1776 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1777 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1779 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1781 # create three canvases
1782 set cscroll .tf.histframe.csb
1783 set canv .tf.histframe.pwclist.canv
1785 -selectbackground $selectbgcolor \
1786 -background $bgcolor -bd 0 \
1787 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1788 .tf.histframe.pwclist add $canv
1789 set canv2 .tf.histframe.pwclist.canv2
1791 -selectbackground $selectbgcolor \
1792 -background $bgcolor -bd 0 -yscrollincr $linespc
1793 .tf.histframe.pwclist add $canv2
1794 set canv3 .tf.histframe.pwclist.canv3
1796 -selectbackground $selectbgcolor \
1797 -background $bgcolor -bd 0 -yscrollincr $linespc
1798 .tf.histframe.pwclist add $canv3
1799 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1800 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1802 # a scroll bar to rule them
1803 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1804 pack $cscroll -side right -fill y
1805 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1806 lappend bglist $canv $canv2 $canv3
1807 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1809 # we have two button bars at bottom of top frame. Bar 1
1811 frame .tf.lbar -height 15
1813 set sha1entry .tf.bar.sha1
1814 set entries $sha1entry
1815 set sha1but .tf.bar.sha1label
1816 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1817 -command gotocommit -width 8
1818 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1819 pack .tf.bar.sha1label -side left
1820 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1821 trace add variable sha1string write sha1change
1822 pack $sha1entry -side left -pady 2
1824 image create bitmap bm-left -data {
1825 #define left_width 16
1826 #define left_height 16
1827 static unsigned char left_bits[] = {
1828 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1829 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1830 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1832 image create bitmap bm-right -data {
1833 #define right_width 16
1834 #define right_height 16
1835 static unsigned char right_bits[] = {
1836 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1837 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1838 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1840 button .tf.bar.leftbut -image bm-left -command goback \
1841 -state disabled -width 26
1842 pack .tf.bar.leftbut -side left -fill y
1843 button .tf.bar.rightbut -image bm-right -command goforw \
1844 -state disabled -width 26
1845 pack .tf.bar.rightbut -side left -fill y
1847 label .tf.bar.rowlabel -text [mc "Row"]
1849 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
1850 -relief sunken -anchor e
1851 label .tf.bar.rowlabel2 -text "/"
1852 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
1853 -relief sunken -anchor e
1854 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
1857 trace add variable selectedline write selectedline_change
1859 # Status label and progress bar
1860 set statusw .tf.bar.status
1861 label $statusw -width 15 -relief sunken
1862 pack $statusw -side left -padx 5
1863 set h [expr {[font metrics uifont -linespace] + 2}]
1864 set progresscanv .tf.bar.progress
1865 canvas $progresscanv -relief sunken -height $h -borderwidth 2
1866 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1867 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1868 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1869 pack $progresscanv -side right -expand 1 -fill x
1870 set progresscoords {0 0}
1873 bind $progresscanv <Configure> adjustprogress
1874 set lastprogupdate [clock clicks -milliseconds]
1875 set progupdatepending 0
1877 # build up the bottom bar of upper window
1878 label .tf.lbar.flabel -text "[mc "Find"] "
1879 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
1880 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
1881 label .tf.lbar.flab2 -text " [mc "commit"] "
1882 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
1884 set gdttype [mc "containing:"]
1885 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
1886 [mc "containing:"] \
1887 [mc "touching paths:"] \
1888 [mc "adding/removing string:"]]
1889 trace add variable gdttype write gdttype_change
1890 pack .tf.lbar.gdttype -side left -fill y
1893 set fstring .tf.lbar.findstring
1894 lappend entries $fstring
1895 entry $fstring -width 30 -font textfont -textvariable findstring
1896 trace add variable findstring write find_change
1897 set findtype [mc "Exact"]
1898 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
1899 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
1900 trace add variable findtype write findcom_change
1901 set findloc [mc "All fields"]
1902 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
1903 [mc "Comments"] [mc "Author"] [mc "Committer"]
1904 trace add variable findloc write find_change
1905 pack .tf.lbar.findloc -side right
1906 pack .tf.lbar.findtype -side right
1907 pack $fstring -side left -expand 1 -fill x
1909 # Finish putting the upper half of the viewer together
1910 pack .tf.lbar -in .tf -side bottom -fill x
1911 pack .tf.bar -in .tf -side bottom -fill x
1912 pack .tf.histframe -fill both -side top -expand 1
1914 .ctop paneconfigure .tf -height $geometry(topheight)
1915 .ctop paneconfigure .tf -width $geometry(topwidth)
1917 # now build up the bottom
1918 panedwindow .pwbottom -orient horizontal
1920 # lower left, a text box over search bar, scroll bar to the right
1921 # if we know window height, then that will set the lower text height, otherwise
1922 # we set lower text height which will drive window height
1923 if {[info exists geometry(main)]} {
1924 frame .bleft -width $geometry(botwidth)
1926 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
1932 button .bleft.top.search -text [mc "Search"] -command dosearch
1933 pack .bleft.top.search -side left -padx 5
1934 set sstring .bleft.top.sstring
1935 entry $sstring -width 20 -font textfont -textvariable searchstring
1936 lappend entries $sstring
1937 trace add variable searchstring write incrsearch
1938 pack $sstring -side left -expand 1 -fill x
1939 radiobutton .bleft.mid.diff -text [mc "Diff"] \
1940 -command changediffdisp -variable diffelide -value {0 0}
1941 radiobutton .bleft.mid.old -text [mc "Old version"] \
1942 -command changediffdisp -variable diffelide -value {0 1}
1943 radiobutton .bleft.mid.new -text [mc "New version"] \
1944 -command changediffdisp -variable diffelide -value {1 0}
1945 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
1946 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
1947 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
1948 -from 1 -increment 1 -to 10000000 \
1949 -validate all -validatecommand "diffcontextvalidate %P" \
1950 -textvariable diffcontextstring
1951 .bleft.mid.diffcontext set $diffcontext
1952 trace add variable diffcontextstring write diffcontextchange
1953 lappend entries .bleft.mid.diffcontext
1954 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
1955 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
1956 -command changeignorespace -variable ignorespace
1957 pack .bleft.mid.ignspace -side left -padx 5
1958 set ctext .bleft.bottom.ctext
1959 text $ctext -background $bgcolor -foreground $fgcolor \
1960 -state disabled -font textfont \
1961 -yscrollcommand scrolltext -wrap none \
1962 -xscrollcommand ".bleft.bottom.sbhorizontal set"
1964 $ctext conf -tabstyle wordprocessor
1966 scrollbar .bleft.bottom.sb -command "$ctext yview"
1967 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
1969 pack .bleft.top -side top -fill x
1970 pack .bleft.mid -side top -fill x
1971 grid $ctext .bleft.bottom.sb -sticky nsew
1972 grid .bleft.bottom.sbhorizontal -sticky ew
1973 grid columnconfigure .bleft.bottom 0 -weight 1
1974 grid rowconfigure .bleft.bottom 0 -weight 1
1975 grid rowconfigure .bleft.bottom 1 -weight 0
1976 pack .bleft.bottom -side top -fill both -expand 1
1977 lappend bglist $ctext
1978 lappend fglist $ctext
1980 $ctext tag conf comment -wrap $wrapcomment
1981 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
1982 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
1983 $ctext tag conf d0 -fore [lindex $diffcolors 0]
1984 $ctext tag conf d1 -fore [lindex $diffcolors 1]
1985 $ctext tag conf m0 -fore red
1986 $ctext tag conf m1 -fore blue
1987 $ctext tag conf m2 -fore green
1988 $ctext tag conf m3 -fore purple
1989 $ctext tag conf m4 -fore brown
1990 $ctext tag conf m5 -fore "#009090"
1991 $ctext tag conf m6 -fore magenta
1992 $ctext tag conf m7 -fore "#808000"
1993 $ctext tag conf m8 -fore "#009000"
1994 $ctext tag conf m9 -fore "#ff0080"
1995 $ctext tag conf m10 -fore cyan
1996 $ctext tag conf m11 -fore "#b07070"
1997 $ctext tag conf m12 -fore "#70b0f0"
1998 $ctext tag conf m13 -fore "#70f0b0"
1999 $ctext tag conf m14 -fore "#f0b070"
2000 $ctext tag conf m15 -fore "#ff70b0"
2001 $ctext tag conf mmax -fore darkgrey
2003 $ctext tag conf mresult -font textfontbold
2004 $ctext tag conf msep -font textfontbold
2005 $ctext tag conf found -back yellow
2007 .pwbottom add .bleft
2008 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2013 radiobutton .bright.mode.patch -text [mc "Patch"] \
2014 -command reselectline -variable cmitmode -value "patch"
2015 radiobutton .bright.mode.tree -text [mc "Tree"] \
2016 -command reselectline -variable cmitmode -value "tree"
2017 grid .bright.mode.patch .bright.mode.tree -sticky ew
2018 pack .bright.mode -side top -fill x
2019 set cflist .bright.cfiles
2020 set indent [font measure mainfont "nn"]
2022 -selectbackground $selectbgcolor \
2023 -background $bgcolor -foreground $fgcolor \
2025 -tabs [list $indent [expr {2 * $indent}]] \
2026 -yscrollcommand ".bright.sb set" \
2027 -cursor [. cget -cursor] \
2028 -spacing1 1 -spacing3 1
2029 lappend bglist $cflist
2030 lappend fglist $cflist
2031 scrollbar .bright.sb -command "$cflist yview"
2032 pack .bright.sb -side right -fill y
2033 pack $cflist -side left -fill both -expand 1
2034 $cflist tag configure highlight \
2035 -background [$cflist cget -selectbackground]
2036 $cflist tag configure bold -font mainfontbold
2038 .pwbottom add .bright
2041 # restore window width & height if known
2042 if {[info exists geometry(main)]} {
2043 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2044 if {$w > [winfo screenwidth .]} {
2045 set w [winfo screenwidth .]
2047 if {$h > [winfo screenheight .]} {
2048 set h [winfo screenheight .]
2050 wm geometry . "${w}x$h"
2054 if {[tk windowingsystem] eq {aqua}} {
2060 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2061 pack .ctop -fill both -expand 1
2062 bindall <1> {selcanvline %W %x %y}
2063 #bindall <B1-Motion> {selcanvline %W %x %y}
2064 if {[tk windowingsystem] == "win32"} {
2065 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2066 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2068 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2069 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2070 if {[tk windowingsystem] eq "aqua"} {
2071 bindall <MouseWheel> {
2072 set delta [expr {- (%D)}]
2073 allcanvs yview scroll $delta units
2077 bindall <2> "canvscan mark %W %x %y"
2078 bindall <B2-Motion> "canvscan dragto %W %x %y"
2079 bindkey <Home> selfirstline
2080 bindkey <End> sellastline
2081 bind . <Key-Up> "selnextline -1"
2082 bind . <Key-Down> "selnextline 1"
2083 bind . <Shift-Key-Up> "dofind -1 0"
2084 bind . <Shift-Key-Down> "dofind 1 0"
2085 bindkey <Key-Right> "goforw"
2086 bindkey <Key-Left> "goback"
2087 bind . <Key-Prior> "selnextpage -1"
2088 bind . <Key-Next> "selnextpage 1"
2089 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2090 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2091 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2092 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2093 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2094 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2095 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2096 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2097 bindkey <Key-space> "$ctext yview scroll 1 pages"
2098 bindkey p "selnextline -1"
2099 bindkey n "selnextline 1"
2102 bindkey i "selnextline -1"
2103 bindkey k "selnextline 1"
2107 bindkey d "$ctext yview scroll 18 units"
2108 bindkey u "$ctext yview scroll -18 units"
2109 bindkey / {dofind 1 1}
2110 bindkey <Key-Return> {dofind 1 1}
2111 bindkey ? {dofind -1 1}
2113 bindkey <F5> updatecommits
2114 bind . <$M1B-q> doquit
2115 bind . <$M1B-f> {dofind 1 1}
2116 bind . <$M1B-g> {dofind 1 0}
2117 bind . <$M1B-r> dosearchback
2118 bind . <$M1B-s> dosearch
2119 bind . <$M1B-equal> {incrfont 1}
2120 bind . <$M1B-plus> {incrfont 1}
2121 bind . <$M1B-KP_Add> {incrfont 1}
2122 bind . <$M1B-minus> {incrfont -1}
2123 bind . <$M1B-KP_Subtract> {incrfont -1}
2124 wm protocol . WM_DELETE_WINDOW doquit
2125 bind . <Destroy> {stop_backends}
2126 bind . <Button-1> "click %W"
2127 bind $fstring <Key-Return> {dofind 1 1}
2128 bind $sha1entry <Key-Return> gotocommit
2129 bind $sha1entry <<PasteSelection>> clearsha1
2130 bind $cflist <1> {sel_flist %W %x %y; break}
2131 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2132 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2133 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
2135 set maincursor [. cget -cursor]
2136 set textcursor [$ctext cget -cursor]
2137 set curtextcursor $textcursor
2139 set rowctxmenu .rowctxmenu
2140 menu $rowctxmenu -tearoff 0
2141 $rowctxmenu add command -label [mc "Diff this -> selected"] \
2142 -command {diffvssel 0}
2143 $rowctxmenu add command -label [mc "Diff selected -> this"] \
2144 -command {diffvssel 1}
2145 $rowctxmenu add command -label [mc "Make patch"] -command mkpatch
2146 $rowctxmenu add command -label [mc "Create tag"] -command mktag
2147 $rowctxmenu add command -label [mc "Write commit to file"] -command writecommit
2148 $rowctxmenu add command -label [mc "Create new branch"] -command mkbranch
2149 $rowctxmenu add command -label [mc "Cherry-pick this commit"] \
2151 $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \
2154 set fakerowmenu .fakerowmenu
2155 menu $fakerowmenu -tearoff 0
2156 $fakerowmenu add command -label [mc "Diff this -> selected"] \
2157 -command {diffvssel 0}
2158 $fakerowmenu add command -label [mc "Diff selected -> this"] \
2159 -command {diffvssel 1}
2160 $fakerowmenu add command -label [mc "Make patch"] -command mkpatch
2161 # $fakerowmenu add command -label [mc "Commit"] -command {mkcommit 0}
2162 # $fakerowmenu add command -label [mc "Commit all"] -command {mkcommit 1}
2163 # $fakerowmenu add command -label [mc "Revert local changes"] -command revertlocal
2165 set headctxmenu .headctxmenu
2166 menu $headctxmenu -tearoff 0
2167 $headctxmenu add command -label [mc "Check out this branch"] \
2169 $headctxmenu add command -label [mc "Remove this branch"] \
2173 set flist_menu .flistctxmenu
2174 menu $flist_menu -tearoff 0
2175 $flist_menu add command -label [mc "Highlight this too"] \
2176 -command {flist_hl 0}
2177 $flist_menu add command -label [mc "Highlight this only"] \
2178 -command {flist_hl 1}
2179 $flist_menu add command -label [mc "External diff"] \
2180 -command {external_diff}
2183 # Windows sends all mouse wheel events to the current focused window, not
2184 # the one where the mouse hovers, so bind those events here and redirect
2185 # to the correct window
2186 proc windows_mousewheel_redirector {W X Y D} {
2187 global canv canv2 canv3
2188 set w [winfo containing -displayof $W $X $Y]
2190 set u [expr {$D < 0 ? 5 : -5}]
2191 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2192 allcanvs yview scroll $u units
2195 $w yview scroll $u units
2201 # Update row number label when selectedline changes
2202 proc selectedline_change {n1 n2 op} {
2203 global selectedline rownumsel
2205 if {$selectedline eq {}} {
2208 set rownumsel [expr {$selectedline + 1}]
2212 # mouse-2 makes all windows scan vertically, but only the one
2213 # the cursor is in scans horizontally
2214 proc canvscan {op w x y} {
2215 global canv canv2 canv3
2216 foreach c [list $canv $canv2 $canv3] {
2225 proc scrollcanv {cscroll f0 f1} {
2226 $cscroll set $f0 $f1
2231 # when we make a key binding for the toplevel, make sure
2232 # it doesn't get triggered when that key is pressed in the
2233 # find string entry widget.
2234 proc bindkey {ev script} {
2237 set escript [bind Entry $ev]
2238 if {$escript == {}} {
2239 set escript [bind Entry <Key>]
2241 foreach e $entries {
2242 bind $e $ev "$escript; break"
2246 # set the focus back to the toplevel for any click outside
2249 global ctext entries
2250 foreach e [concat $entries $ctext] {
2251 if {$w == $e} return
2256 # Adjust the progress bar for a change in requested extent or canvas size
2257 proc adjustprogress {} {
2258 global progresscanv progressitem progresscoords
2259 global fprogitem fprogcoord lastprogupdate progupdatepending
2260 global rprogitem rprogcoord
2262 set w [expr {[winfo width $progresscanv] - 4}]
2263 set x0 [expr {$w * [lindex $progresscoords 0]}]
2264 set x1 [expr {$w * [lindex $progresscoords 1]}]
2265 set h [winfo height $progresscanv]
2266 $progresscanv coords $progressitem $x0 0 $x1 $h
2267 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2268 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2269 set now [clock clicks -milliseconds]
2270 if {$now >= $lastprogupdate + 100} {
2271 set progupdatepending 0
2273 } elseif {!$progupdatepending} {
2274 set progupdatepending 1
2275 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2279 proc doprogupdate {} {
2280 global lastprogupdate progupdatepending
2282 if {$progupdatepending} {
2283 set progupdatepending 0
2284 set lastprogupdate [clock clicks -milliseconds]
2289 proc savestuff {w} {
2290 global canv canv2 canv3 mainfont textfont uifont tabstop
2291 global stuffsaved findmergefiles maxgraphpct
2292 global maxwidth showneartags showlocalchanges
2293 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2294 global cmitmode wrapcomment datetimeformat limitdiffs
2295 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2296 global autoselect extdifftool
2298 if {$stuffsaved} return
2299 if {![winfo viewable .]} return
2301 set f [open "~/.gitk-new" w]
2302 puts $f [list set mainfont $mainfont]
2303 puts $f [list set textfont $textfont]
2304 puts $f [list set uifont $uifont]
2305 puts $f [list set tabstop $tabstop]
2306 puts $f [list set findmergefiles $findmergefiles]
2307 puts $f [list set maxgraphpct $maxgraphpct]
2308 puts $f [list set maxwidth $maxwidth]
2309 puts $f [list set cmitmode $cmitmode]
2310 puts $f [list set wrapcomment $wrapcomment]
2311 puts $f [list set autoselect $autoselect]
2312 puts $f [list set showneartags $showneartags]
2313 puts $f [list set showlocalchanges $showlocalchanges]
2314 puts $f [list set datetimeformat $datetimeformat]
2315 puts $f [list set limitdiffs $limitdiffs]
2316 puts $f [list set bgcolor $bgcolor]
2317 puts $f [list set fgcolor $fgcolor]
2318 puts $f [list set colors $colors]
2319 puts $f [list set diffcolors $diffcolors]
2320 puts $f [list set diffcontext $diffcontext]
2321 puts $f [list set selectbgcolor $selectbgcolor]
2322 puts $f [list set extdifftool $extdifftool]
2324 puts $f "set geometry(main) [wm geometry .]"
2325 puts $f "set geometry(topwidth) [winfo width .tf]"
2326 puts $f "set geometry(topheight) [winfo height .tf]"
2327 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2328 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2329 puts $f "set geometry(botwidth) [winfo width .bleft]"
2330 puts $f "set geometry(botheight) [winfo height .bleft]"
2332 puts -nonewline $f "set permviews {"
2333 for {set v 0} {$v < $nextviewnum} {incr v} {
2334 if {$viewperm($v)} {
2335 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2340 file rename -force "~/.gitk-new" "~/.gitk"
2345 proc resizeclistpanes {win w} {
2347 if {[info exists oldwidth($win)]} {
2348 set s0 [$win sash coord 0]
2349 set s1 [$win sash coord 1]
2351 set sash0 [expr {int($w/2 - 2)}]
2352 set sash1 [expr {int($w*5/6 - 2)}]
2354 set factor [expr {1.0 * $w / $oldwidth($win)}]
2355 set sash0 [expr {int($factor * [lindex $s0 0])}]
2356 set sash1 [expr {int($factor * [lindex $s1 0])}]
2360 if {$sash1 < $sash0 + 20} {
2361 set sash1 [expr {$sash0 + 20}]
2363 if {$sash1 > $w - 10} {
2364 set sash1 [expr {$w - 10}]
2365 if {$sash0 > $sash1 - 20} {
2366 set sash0 [expr {$sash1 - 20}]
2370 $win sash place 0 $sash0 [lindex $s0 1]
2371 $win sash place 1 $sash1 [lindex $s1 1]
2373 set oldwidth($win) $w
2376 proc resizecdetpanes {win w} {
2378 if {[info exists oldwidth($win)]} {
2379 set s0 [$win sash coord 0]
2381 set sash0 [expr {int($w*3/4 - 2)}]
2383 set factor [expr {1.0 * $w / $oldwidth($win)}]
2384 set sash0 [expr {int($factor * [lindex $s0 0])}]
2388 if {$sash0 > $w - 15} {
2389 set sash0 [expr {$w - 15}]
2392 $win sash place 0 $sash0 [lindex $s0 1]
2394 set oldwidth($win) $w
2397 proc allcanvs args {
2398 global canv canv2 canv3
2404 proc bindall {event action} {
2405 global canv canv2 canv3
2406 bind $canv $event $action
2407 bind $canv2 $event $action
2408 bind $canv3 $event $action
2414 if {[winfo exists $w]} {
2419 wm title $w [mc "About gitk"]
2420 message $w.m -text [mc "
2421 Gitk - a commit viewer for git
2423 Copyright © 2005-2008 Paul Mackerras
2425 Use and redistribute under the terms of the GNU General Public License"] \
2426 -justify center -aspect 400 -border 2 -bg white -relief groove
2427 pack $w.m -side top -fill x -padx 2 -pady 2
2428 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2429 pack $w.ok -side bottom
2430 bind $w <Visibility> "focus $w.ok"
2431 bind $w <Key-Escape> "destroy $w"
2432 bind $w <Key-Return> "destroy $w"
2437 if {[winfo exists $w]} {
2441 if {[tk windowingsystem] eq {aqua}} {
2447 wm title $w [mc "Gitk key bindings"]
2448 message $w.m -text "
2449 [mc "Gitk key bindings:"]
2451 [mc "<%s-Q> Quit" $M1T]
2452 [mc "<Home> Move to first commit"]
2453 [mc "<End> Move to last commit"]
2454 [mc "<Up>, p, i Move up one commit"]
2455 [mc "<Down>, n, k Move down one commit"]
2456 [mc "<Left>, z, j Go back in history list"]
2457 [mc "<Right>, x, l Go forward in history list"]
2458 [mc "<PageUp> Move up one page in commit list"]
2459 [mc "<PageDown> Move down one page in commit list"]
2460 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2461 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2462 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2463 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2464 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2465 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2466 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2467 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2468 [mc "<Delete>, b Scroll diff view up one page"]
2469 [mc "<Backspace> Scroll diff view up one page"]
2470 [mc "<Space> Scroll diff view down one page"]
2471 [mc "u Scroll diff view up 18 lines"]
2472 [mc "d Scroll diff view down 18 lines"]
2473 [mc "<%s-F> Find" $M1T]
2474 [mc "<%s-G> Move to next find hit" $M1T]
2475 [mc "<Return> Move to next find hit"]
2476 [mc "/ Move to next find hit, or redo find"]
2477 [mc "? Move to previous find hit"]
2478 [mc "f Scroll diff view to next file"]
2479 [mc "<%s-S> Search for next hit in diff view" $M1T]
2480 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2481 [mc "<%s-KP+> Increase font size" $M1T]
2482 [mc "<%s-plus> Increase font size" $M1T]
2483 [mc "<%s-KP-> Decrease font size" $M1T]
2484 [mc "<%s-minus> Decrease font size" $M1T]
2487 -justify left -bg white -border 2 -relief groove
2488 pack $w.m -side top -fill both -padx 2 -pady 2
2489 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2490 pack $w.ok -side bottom
2491 bind $w <Visibility> "focus $w.ok"
2492 bind $w <Key-Escape> "destroy $w"
2493 bind $w <Key-Return> "destroy $w"
2496 # Procedures for manipulating the file list window at the
2497 # bottom right of the overall window.
2499 proc treeview {w l openlevs} {
2500 global treecontents treediropen treeheight treeparent treeindex
2510 set treecontents() {}
2511 $w conf -state normal
2513 while {[string range $f 0 $prefixend] ne $prefix} {
2514 if {$lev <= $openlevs} {
2515 $w mark set e:$treeindex($prefix) "end -1c"
2516 $w mark gravity e:$treeindex($prefix) left
2518 set treeheight($prefix) $ht
2519 incr ht [lindex $htstack end]
2520 set htstack [lreplace $htstack end end]
2521 set prefixend [lindex $prefendstack end]
2522 set prefendstack [lreplace $prefendstack end end]
2523 set prefix [string range $prefix 0 $prefixend]
2526 set tail [string range $f [expr {$prefixend+1}] end]
2527 while {[set slash [string first "/" $tail]] >= 0} {
2530 lappend prefendstack $prefixend
2531 incr prefixend [expr {$slash + 1}]
2532 set d [string range $tail 0 $slash]
2533 lappend treecontents($prefix) $d
2534 set oldprefix $prefix
2536 set treecontents($prefix) {}
2537 set treeindex($prefix) [incr ix]
2538 set treeparent($prefix) $oldprefix
2539 set tail [string range $tail [expr {$slash+1}] end]
2540 if {$lev <= $openlevs} {
2542 set treediropen($prefix) [expr {$lev < $openlevs}]
2543 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2544 $w mark set d:$ix "end -1c"
2545 $w mark gravity d:$ix left
2547 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2549 $w image create end -align center -image $bm -padx 1 \
2551 $w insert end $d [highlight_tag $prefix]
2552 $w mark set s:$ix "end -1c"
2553 $w mark gravity s:$ix left
2558 if {$lev <= $openlevs} {
2561 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2563 $w insert end $tail [highlight_tag $f]
2565 lappend treecontents($prefix) $tail
2568 while {$htstack ne {}} {
2569 set treeheight($prefix) $ht
2570 incr ht [lindex $htstack end]
2571 set htstack [lreplace $htstack end end]
2572 set prefixend [lindex $prefendstack end]
2573 set prefendstack [lreplace $prefendstack end end]
2574 set prefix [string range $prefix 0 $prefixend]
2576 $w conf -state disabled
2579 proc linetoelt {l} {
2580 global treeheight treecontents
2585 foreach e $treecontents($prefix) {
2590 if {[string index $e end] eq "/"} {
2591 set n $treeheight($prefix$e)
2603 proc highlight_tree {y prefix} {
2604 global treeheight treecontents cflist
2606 foreach e $treecontents($prefix) {
2608 if {[highlight_tag $path] ne {}} {
2609 $cflist tag add bold $y.0 "$y.0 lineend"
2612 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2613 set y [highlight_tree $y $path]
2619 proc treeclosedir {w dir} {
2620 global treediropen treeheight treeparent treeindex
2622 set ix $treeindex($dir)
2623 $w conf -state normal
2624 $w delete s:$ix e:$ix
2625 set treediropen($dir) 0
2626 $w image configure a:$ix -image tri-rt
2627 $w conf -state disabled
2628 set n [expr {1 - $treeheight($dir)}]
2629 while {$dir ne {}} {
2630 incr treeheight($dir) $n
2631 set dir $treeparent($dir)
2635 proc treeopendir {w dir} {
2636 global treediropen treeheight treeparent treecontents treeindex
2638 set ix $treeindex($dir)
2639 $w conf -state normal
2640 $w image configure a:$ix -image tri-dn
2641 $w mark set e:$ix s:$ix
2642 $w mark gravity e:$ix right
2645 set n [llength $treecontents($dir)]
2646 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2649 incr treeheight($x) $n
2651 foreach e $treecontents($dir) {
2653 if {[string index $e end] eq "/"} {
2654 set iy $treeindex($de)
2655 $w mark set d:$iy e:$ix
2656 $w mark gravity d:$iy left
2657 $w insert e:$ix $str
2658 set treediropen($de) 0
2659 $w image create e:$ix -align center -image tri-rt -padx 1 \
2661 $w insert e:$ix $e [highlight_tag $de]
2662 $w mark set s:$iy e:$ix
2663 $w mark gravity s:$iy left
2664 set treeheight($de) 1
2666 $w insert e:$ix $str
2667 $w insert e:$ix $e [highlight_tag $de]
2670 $w mark gravity e:$ix left
2671 $w conf -state disabled
2672 set treediropen($dir) 1
2673 set top [lindex [split [$w index @0,0] .] 0]
2674 set ht [$w cget -height]
2675 set l [lindex [split [$w index s:$ix] .] 0]
2678 } elseif {$l + $n + 1 > $top + $ht} {
2679 set top [expr {$l + $n + 2 - $ht}]
2687 proc treeclick {w x y} {
2688 global treediropen cmitmode ctext cflist cflist_top
2690 if {$cmitmode ne "tree"} return
2691 if {![info exists cflist_top]} return
2692 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2693 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2694 $cflist tag add highlight $l.0 "$l.0 lineend"
2700 set e [linetoelt $l]
2701 if {[string index $e end] ne "/"} {
2703 } elseif {$treediropen($e)} {
2710 proc setfilelist {id} {
2711 global treefilelist cflist
2713 treeview $cflist $treefilelist($id) 0
2716 image create bitmap tri-rt -background black -foreground blue -data {
2717 #define tri-rt_width 13
2718 #define tri-rt_height 13
2719 static unsigned char tri-rt_bits[] = {
2720 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2721 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2724 #define tri-rt-mask_width 13
2725 #define tri-rt-mask_height 13
2726 static unsigned char tri-rt-mask_bits[] = {
2727 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2728 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2731 image create bitmap tri-dn -background black -foreground blue -data {
2732 #define tri-dn_width 13
2733 #define tri-dn_height 13
2734 static unsigned char tri-dn_bits[] = {
2735 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2736 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2739 #define tri-dn-mask_width 13
2740 #define tri-dn-mask_height 13
2741 static unsigned char tri-dn-mask_bits[] = {
2742 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2743 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2747 image create bitmap reficon-T -background black -foreground yellow -data {
2748 #define tagicon_width 13
2749 #define tagicon_height 9
2750 static unsigned char tagicon_bits[] = {
2751 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2752 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2754 #define tagicon-mask_width 13
2755 #define tagicon-mask_height 9
2756 static unsigned char tagicon-mask_bits[] = {
2757 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2758 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2761 #define headicon_width 13
2762 #define headicon_height 9
2763 static unsigned char headicon_bits[] = {
2764 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2765 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2768 #define headicon-mask_width 13
2769 #define headicon-mask_height 9
2770 static unsigned char headicon-mask_bits[] = {
2771 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2772 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2774 image create bitmap reficon-H -background black -foreground green \
2775 -data $rectdata -maskdata $rectmask
2776 image create bitmap reficon-o -background black -foreground "#ddddff" \
2777 -data $rectdata -maskdata $rectmask
2779 proc init_flist {first} {
2780 global cflist cflist_top difffilestart
2782 $cflist conf -state normal
2783 $cflist delete 0.0 end
2785 $cflist insert end $first
2787 $cflist tag add highlight 1.0 "1.0 lineend"
2789 catch {unset cflist_top}
2791 $cflist conf -state disabled
2792 set difffilestart {}
2795 proc highlight_tag {f} {
2796 global highlight_paths
2798 foreach p $highlight_paths {
2799 if {[string match $p $f]} {
2806 proc highlight_filelist {} {
2807 global cmitmode cflist
2809 $cflist conf -state normal
2810 if {$cmitmode ne "tree"} {
2811 set end [lindex [split [$cflist index end] .] 0]
2812 for {set l 2} {$l < $end} {incr l} {
2813 set line [$cflist get $l.0 "$l.0 lineend"]
2814 if {[highlight_tag $line] ne {}} {
2815 $cflist tag add bold $l.0 "$l.0 lineend"
2821 $cflist conf -state disabled
2824 proc unhighlight_filelist {} {
2827 $cflist conf -state normal
2828 $cflist tag remove bold 1.0 end
2829 $cflist conf -state disabled
2832 proc add_flist {fl} {
2835 $cflist conf -state normal
2837 $cflist insert end "\n"
2838 $cflist insert end $f [highlight_tag $f]
2840 $cflist conf -state disabled
2843 proc sel_flist {w x y} {
2844 global ctext difffilestart cflist cflist_top cmitmode
2846 if {$cmitmode eq "tree"} return
2847 if {![info exists cflist_top]} return
2848 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2849 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2850 $cflist tag add highlight $l.0 "$l.0 lineend"
2855 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2859 proc pop_flist_menu {w X Y x y} {
2860 global ctext cflist cmitmode flist_menu flist_menu_file
2861 global treediffs diffids
2864 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2866 if {$cmitmode eq "tree"} {
2867 set e [linetoelt $l]
2868 if {[string index $e end] eq "/"} return
2870 set e [lindex $treediffs($diffids) [expr {$l-2}]]
2872 set flist_menu_file $e
2873 set xdiffstate "normal"
2874 if {$cmitmode eq "tree"} {
2875 set xdiffstate "disabled"
2877 # Disable "External diff" item in tree mode
2878 $flist_menu entryconf 2 -state $xdiffstate
2879 tk_popup $flist_menu $X $Y
2882 proc flist_hl {only} {
2883 global flist_menu_file findstring gdttype
2885 set x [shellquote $flist_menu_file]
2886 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
2889 append findstring " " $x
2891 set gdttype [mc "touching paths:"]
2894 proc save_file_from_commit {filename output what} {
2897 if {[catch {exec git show $filename -- > $output} err]} {
2898 if {[string match "fatal: bad revision *" $err]} {
2901 error_popup "Error getting \"$filename\" from $what: $err"
2907 proc external_diff_get_one_file {diffid filename diffdir} {
2908 global nullid nullid2 nullfile
2911 if {$diffid == $nullid} {
2912 set difffile [file join [file dirname $gitdir] $filename]
2913 if {[file exists $difffile]} {
2918 if {$diffid == $nullid2} {
2919 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
2920 return [save_file_from_commit :$filename $difffile index]
2922 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
2923 return [save_file_from_commit $diffid:$filename $difffile \
2927 proc external_diff {} {
2928 global gitktmpdir nullid nullid2
2929 global flist_menu_file
2932 global gitdir extdifftool
2934 if {[llength $diffids] == 1} {
2935 # no reference commit given
2936 set diffidto [lindex $diffids 0]
2937 if {$diffidto eq $nullid} {
2938 # diffing working copy with index
2939 set diffidfrom $nullid2
2940 } elseif {$diffidto eq $nullid2} {
2941 # diffing index with HEAD
2942 set diffidfrom "HEAD"
2944 # use first parent commit
2945 global parentlist selectedline
2946 set diffidfrom [lindex $parentlist $selectedline 0]
2949 set diffidfrom [lindex $diffids 0]
2950 set diffidto [lindex $diffids 1]
2953 # make sure that several diffs wont collide
2954 if {![info exists gitktmpdir]} {
2955 set gitktmpdir [file join [file dirname $gitdir] \
2956 [format ".gitk-tmp.%s" [pid]]]
2957 if {[catch {file mkdir $gitktmpdir} err]} {
2958 error_popup "Error creating temporary directory $gitktmpdir: $err"
2965 set diffdir [file join $gitktmpdir $diffnum]
2966 if {[catch {file mkdir $diffdir} err]} {
2967 error_popup "Error creating temporary directory $diffdir: $err"
2971 # gather files to diff
2972 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
2973 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
2975 if {$difffromfile ne {} && $difftofile ne {}} {
2976 set cmd [concat | [shellsplit $extdifftool] \
2977 [list $difffromfile $difftofile]]
2978 if {[catch {set fl [open $cmd r]} err]} {
2979 file delete -force $diffdir
2980 error_popup [mc "$extdifftool: command failed: $err"]
2982 fconfigure $fl -blocking 0
2983 filerun $fl [list delete_at_eof $fl $diffdir]
2988 # delete $dir when we see eof on $f (presumably because the child has exited)
2989 proc delete_at_eof {f dir} {
2990 while {[gets $f line] >= 0} {}
2992 if {[catch {close $f} err]} {
2993 error_popup "External diff viewer failed: $err"
2995 file delete -force $dir
3001 # Functions for adding and removing shell-type quoting
3003 proc shellquote {str} {
3004 if {![string match "*\['\"\\ \t]*" $str]} {
3007 if {![string match "*\['\"\\]*" $str]} {
3010 if {![string match "*'*" $str]} {
3013 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3016 proc shellarglist {l} {
3022 append str [shellquote $a]
3027 proc shelldequote {str} {
3032 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3033 append ret [string range $str $used end]
3034 set used [string length $str]
3037 set first [lindex $first 0]
3038 set ch [string index $str $first]
3039 if {$first > $used} {
3040 append ret [string range $str $used [expr {$first - 1}]]
3043 if {$ch eq " " || $ch eq "\t"} break
3046 set first [string first "'" $str $used]
3048 error "unmatched single-quote"
3050 append ret [string range $str $used [expr {$first - 1}]]
3055 if {$used >= [string length $str]} {
3056 error "trailing backslash"
3058 append ret [string index $str $used]
3063 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3064 error "unmatched double-quote"
3066 set first [lindex $first 0]
3067 set ch [string index $str $first]
3068 if {$first > $used} {
3069 append ret [string range $str $used [expr {$first - 1}]]
3072 if {$ch eq "\""} break
3074 append ret [string index $str $used]
3078 return [list $used $ret]
3081 proc shellsplit {str} {
3084 set str [string trimleft $str]
3085 if {$str eq {}} break
3086 set dq [shelldequote $str]
3087 set n [lindex $dq 0]
3088 set word [lindex $dq 1]
3089 set str [string range $str $n end]
3095 # Code to implement multiple views
3097 proc newview {ishighlight} {
3098 global nextviewnum newviewname newviewperm newishighlight
3099 global newviewargs revtreeargs viewargscmd newviewargscmd curview
3101 set newishighlight $ishighlight
3103 if {[winfo exists $top]} {
3107 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3108 set newviewperm($nextviewnum) 0
3109 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
3110 set newviewargscmd($nextviewnum) $viewargscmd($curview)
3111 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3116 global viewname viewperm newviewname newviewperm
3117 global viewargs newviewargs viewargscmd newviewargscmd
3119 set top .gitkvedit-$curview
3120 if {[winfo exists $top]} {
3124 set newviewname($curview) $viewname($curview)
3125 set newviewperm($curview) $viewperm($curview)
3126 set newviewargs($curview) [shellarglist $viewargs($curview)]
3127 set newviewargscmd($curview) $viewargscmd($curview)
3128 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3131 proc vieweditor {top n title} {
3132 global newviewname newviewperm viewfiles bgcolor
3135 wm title $top $title
3136 label $top.nl -text [mc "Name"]
3137 entry $top.name -width 20 -textvariable newviewname($n)
3138 grid $top.nl $top.name -sticky w -pady 5
3139 checkbutton $top.perm -text [mc "Remember this view"] \
3140 -variable newviewperm($n)
3141 grid $top.perm - -pady 5 -sticky w
3142 message $top.al -aspect 1000 \
3143 -text [mc "Commits to include (arguments to git log):"]
3144 grid $top.al - -sticky w -pady 5
3145 entry $top.args -width 50 -textvariable newviewargs($n) \
3146 -background $bgcolor
3147 grid $top.args - -sticky ew -padx 5
3149 message $top.ac -aspect 1000 \
3150 -text [mc "Command to generate more commits to include:"]
3151 grid $top.ac - -sticky w -pady 5
3152 entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
3154 grid $top.argscmd - -sticky ew -padx 5
3156 message $top.l -aspect 1000 \
3157 -text [mc "Enter files and directories to include, one per line:"]
3158 grid $top.l - -sticky w
3159 text $top.t -width 40 -height 10 -background $bgcolor -font uifont
3160 if {[info exists viewfiles($n)]} {
3161 foreach f $viewfiles($n) {
3162 $top.t insert end $f
3163 $top.t insert end "\n"
3165 $top.t delete {end - 1c} end
3166 $top.t mark set insert 0.0
3168 grid $top.t - -sticky ew -padx 5
3170 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3171 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3172 grid $top.buts.ok $top.buts.can
3173 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3174 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3175 grid $top.buts - -pady 10 -sticky ew
3179 proc doviewmenu {m first cmd op argv} {
3180 set nmenu [$m index end]
3181 for {set i $first} {$i <= $nmenu} {incr i} {
3182 if {[$m entrycget $i -command] eq $cmd} {
3183 eval $m $op $i $argv
3189 proc allviewmenus {n op args} {
3192 doviewmenu .bar.view 5 [list showview $n] $op $args
3193 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3196 proc newviewok {top n} {
3197 global nextviewnum newviewperm newviewname newishighlight
3198 global viewname viewfiles viewperm selectedview curview
3199 global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
3202 set newargs [shellsplit $newviewargs($n)]
3204 error_popup "[mc "Error in commit selection arguments:"] $err"
3210 foreach f [split [$top.t get 0.0 end] "\n"] {
3211 set ft [string trim $f]
3216 if {![info exists viewfiles($n)]} {
3217 # creating a new view
3219 set viewname($n) $newviewname($n)
3220 set viewperm($n) $newviewperm($n)
3221 set viewfiles($n) $files
3222 set viewargs($n) $newargs
3223 set viewargscmd($n) $newviewargscmd($n)
3225 if {!$newishighlight} {
3228 run addvhighlight $n
3231 # editing an existing view
3232 set viewperm($n) $newviewperm($n)
3233 if {$newviewname($n) ne $viewname($n)} {
3234 set viewname($n) $newviewname($n)
3235 doviewmenu .bar.view 5 [list showview $n] \
3236 entryconf [list -label $viewname($n)]
3237 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3238 # entryconf [list -label $viewname($n) -value $viewname($n)]
3240 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3241 $newviewargscmd($n) ne $viewargscmd($n)} {
3242 set viewfiles($n) $files
3243 set viewargs($n) $newargs
3244 set viewargscmd($n) $newviewargscmd($n)
3245 if {$curview == $n} {
3250 catch {destroy $top}
3254 global curview viewperm hlview selectedhlview
3256 if {$curview == 0} return
3257 if {[info exists hlview] && $hlview == $curview} {
3258 set selectedhlview [mc "None"]
3261 allviewmenus $curview delete
3262 set viewperm($curview) 0
3266 proc addviewmenu {n} {
3267 global viewname viewhlmenu
3269 .bar.view add radiobutton -label $viewname($n) \
3270 -command [list showview $n] -variable selectedview -value $n
3271 #$viewhlmenu add radiobutton -label $viewname($n) \
3272 # -command [list addvhighlight $n] -variable selectedhlview
3276 global curview cached_commitrow ordertok
3277 global displayorder parentlist rowidlist rowisopt rowfinal
3278 global colormap rowtextx nextcolor canvxmax
3279 global numcommits viewcomplete
3280 global selectedline currentid canv canvy0
3282 global pending_select mainheadid
3285 global hlview selectedhlview commitinterest
3287 if {$n == $curview} return
3289 set ymax [lindex [$canv cget -scrollregion] 3]
3290 set span [$canv yview]
3291 set ytop [expr {[lindex $span 0] * $ymax}]
3292 set ybot [expr {[lindex $span 1] * $ymax}]
3293 set yscreen [expr {($ybot - $ytop) / 2}]
3294 if {$selectedline ne {}} {
3295 set selid $currentid
3296 set y [yc $selectedline]
3297 if {$ytop < $y && $y < $ybot} {
3298 set yscreen [expr {$y - $ytop}]
3300 } elseif {[info exists pending_select]} {
3301 set selid $pending_select
3302 unset pending_select
3306 catch {unset treediffs}
3308 if {[info exists hlview] && $hlview == $n} {
3310 set selectedhlview [mc "None"]
3312 catch {unset commitinterest}
3313 catch {unset cached_commitrow}
3314 catch {unset ordertok}
3318 .bar.view entryconf [mc "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3319 .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3322 if {![info exists viewcomplete($n)]} {
3324 set pending_select $selid
3335 set numcommits $commitidx($n)
3337 catch {unset colormap}
3338 catch {unset rowtextx}
3340 set canvxmax [$canv cget -width]
3346 if {$selid ne {} && [commitinview $selid $n]} {
3347 set row [rowofcommit $selid]
3348 # try to get the selected row in the same position on the screen
3349 set ymax [lindex [$canv cget -scrollregion] 3]
3350 set ytop [expr {[yc $row] - $yscreen}]
3354 set yf [expr {$ytop * 1.0 / $ymax}]
3356 allcanvs yview moveto $yf
3360 } elseif {$mainheadid ne {} && [commitinview $mainheadid $curview]} {
3361 selectline [rowofcommit $mainheadid] 1
3362 } elseif {!$viewcomplete($n)} {
3364 set pending_select $selid
3366 set pending_select $mainheadid
3369 set row [first_real_row]
3370 if {$row < $numcommits} {
3374 if {!$viewcomplete($n)} {
3375 if {$numcommits == 0} {
3376 show_status [mc "Reading commits..."]
3378 } elseif {$numcommits == 0} {
3379 show_status [mc "No commits selected"]
3383 # Stuff relating to the highlighting facility
3385 proc ishighlighted {id} {
3386 global vhighlights fhighlights nhighlights rhighlights
3388 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3389 return $nhighlights($id)
3391 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3392 return $vhighlights($id)
3394 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3395 return $fhighlights($id)
3397 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3398 return $rhighlights($id)
3403 proc bolden {row font} {
3404 global canv linehtag selectedline boldrows
3406 lappend boldrows $row
3407 $canv itemconf $linehtag($row) -font $font
3408 if {$row == $selectedline} {
3410 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3411 -outline {{}} -tags secsel \
3412 -fill [$canv cget -selectbackground]]
3417 proc bolden_name {row font} {
3418 global canv2 linentag selectedline boldnamerows
3420 lappend boldnamerows $row
3421 $canv2 itemconf $linentag($row) -font $font
3422 if {$row == $selectedline} {
3423 $canv2 delete secsel
3424 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3425 -outline {{}} -tags secsel \
3426 -fill [$canv2 cget -selectbackground]]
3435 foreach row $boldrows {
3436 if {![ishighlighted [commitonrow $row]]} {
3437 bolden $row mainfont
3439 lappend stillbold $row
3442 set boldrows $stillbold
3445 proc addvhighlight {n} {
3446 global hlview viewcomplete curview vhl_done commitidx
3448 if {[info exists hlview]} {
3452 if {$n != $curview && ![info exists viewcomplete($n)]} {
3455 set vhl_done $commitidx($hlview)
3456 if {$vhl_done > 0} {
3461 proc delvhighlight {} {
3462 global hlview vhighlights
3464 if {![info exists hlview]} return
3466 catch {unset vhighlights}
3470 proc vhighlightmore {} {
3471 global hlview vhl_done commitidx vhighlights curview
3473 set max $commitidx($hlview)
3474 set vr [visiblerows]
3475 set r0 [lindex $vr 0]
3476 set r1 [lindex $vr 1]
3477 for {set i $vhl_done} {$i < $max} {incr i} {
3478 set id [commitonrow $i $hlview]
3479 if {[commitinview $id $curview]} {
3480 set row [rowofcommit $id]
3481 if {$r0 <= $row && $row <= $r1} {
3482 if {![highlighted $row]} {
3483 bolden $row mainfontbold
3485 set vhighlights($id) 1
3493 proc askvhighlight {row id} {
3494 global hlview vhighlights iddrawn
3496 if {[commitinview $id $hlview]} {
3497 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
3498 bolden $row mainfontbold
3500 set vhighlights($id) 1
3502 set vhighlights($id) 0
3506 proc hfiles_change {} {
3507 global highlight_files filehighlight fhighlights fh_serial
3508 global highlight_paths gdttype
3510 if {[info exists filehighlight]} {
3511 # delete previous highlights
3512 catch {close $filehighlight}
3514 catch {unset fhighlights}
3516 unhighlight_filelist
3518 set highlight_paths {}
3519 after cancel do_file_hl $fh_serial
3521 if {$highlight_files ne {}} {
3522 after 300 do_file_hl $fh_serial
3526 proc gdttype_change {name ix op} {
3527 global gdttype highlight_files findstring findpattern
3530 if {$findstring ne {}} {
3531 if {$gdttype eq [mc "containing:"]} {
3532 if {$highlight_files ne {}} {
3533 set highlight_files {}
3538 if {$findpattern ne {}} {
3542 set highlight_files $findstring
3547 # enable/disable findtype/findloc menus too
3550 proc find_change {name ix op} {
3551 global gdttype findstring highlight_files
3554 if {$gdttype eq [mc "containing:"]} {
3557 if {$highlight_files ne $findstring} {
3558 set highlight_files $findstring
3565 proc findcom_change args {
3566 global nhighlights boldnamerows
3567 global findpattern findtype findstring gdttype
3570 # delete previous highlights, if any
3571 foreach row $boldnamerows {
3572 bolden_name $row mainfont
3575 catch {unset nhighlights}
3578 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
3580 } elseif {$findtype eq [mc "Regexp"]} {
3581 set findpattern $findstring
3583 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3585 set findpattern "*$e*"
3589 proc makepatterns {l} {
3592 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3593 if {[string index $ee end] eq "/"} {
3603 proc do_file_hl {serial} {
3604 global highlight_files filehighlight highlight_paths gdttype fhl_list
3606 if {$gdttype eq [mc "touching paths:"]} {
3607 if {[catch {set paths [shellsplit $highlight_files]}]} return
3608 set highlight_paths [makepatterns $paths]
3610 set gdtargs [concat -- $paths]
3611 } elseif {$gdttype eq [mc "adding/removing string:"]} {
3612 set gdtargs [list "-S$highlight_files"]
3614 # must be "containing:", i.e. we're searching commit info
3617 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3618 set filehighlight [open $cmd r+]
3619 fconfigure $filehighlight -blocking 0
3620 filerun $filehighlight readfhighlight
3626 proc flushhighlights {} {
3627 global filehighlight fhl_list
3629 if {[info exists filehighlight]} {
3631 puts $filehighlight ""
3632 flush $filehighlight
3636 proc askfilehighlight {row id} {
3637 global filehighlight fhighlights fhl_list
3639 lappend fhl_list $id
3640 set fhighlights($id) -1
3641 puts $filehighlight $id
3644 proc readfhighlight {} {
3645 global filehighlight fhighlights curview iddrawn
3646 global fhl_list find_dirn
3648 if {![info exists filehighlight]} {
3652 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
3653 set line [string trim $line]
3654 set i [lsearch -exact $fhl_list $line]
3655 if {$i < 0} continue
3656 for {set j 0} {$j < $i} {incr j} {
3657 set id [lindex $fhl_list $j]
3658 set fhighlights($id) 0
3660 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
3661 if {$line eq {}} continue
3662 if {![commitinview $line $curview]} continue
3663 set row [rowofcommit $line]
3664 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
3665 bolden $row mainfontbold
3667 set fhighlights($line) 1
3669 if {[eof $filehighlight]} {
3671 puts "oops, git diff-tree died"
3672 catch {close $filehighlight}
3676 if {[info exists find_dirn]} {
3682 proc doesmatch {f} {
3683 global findtype findpattern
3685 if {$findtype eq [mc "Regexp"]} {
3686 return [regexp $findpattern $f]
3687 } elseif {$findtype eq [mc "IgnCase"]} {
3688 return [string match -nocase $findpattern $f]
3690 return [string match $findpattern $f]
3694 proc askfindhighlight {row id} {
3695 global nhighlights commitinfo iddrawn
3697 global markingmatches
3699 if {![info exists commitinfo($id)]} {
3702 set info $commitinfo($id)
3704 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
3705 foreach f $info ty $fldtypes {
3706 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
3708 if {$ty eq [mc "Author"]} {
3715 if {$isbold && [info exists iddrawn($id)]} {
3716 if {![ishighlighted $id]} {
3717 bolden $row mainfontbold
3719 bolden_name $row mainfontbold
3722 if {$markingmatches} {
3723 markrowmatches $row $id
3726 set nhighlights($id) $isbold
3729 proc markrowmatches {row id} {
3730 global canv canv2 linehtag linentag commitinfo findloc
3732 set headline [lindex $commitinfo($id) 0]
3733 set author [lindex $commitinfo($id) 1]
3734 $canv delete match$row
3735 $canv2 delete match$row
3736 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
3737 set m [findmatches $headline]
3739 markmatches $canv $row $headline $linehtag($row) $m \
3740 [$canv itemcget $linehtag($row) -font] $row
3743 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
3744 set m [findmatches $author]
3746 markmatches $canv2 $row $author $linentag($row) $m \
3747 [$canv2 itemcget $linentag($row) -font] $row
3752 proc vrel_change {name ix op} {
3753 global highlight_related
3756 if {$highlight_related ne [mc "None"]} {
3761 # prepare for testing whether commits are descendents or ancestors of a
3762 proc rhighlight_sel {a} {
3763 global descendent desc_todo ancestor anc_todo
3764 global highlight_related
3766 catch {unset descendent}
3767 set desc_todo [list $a]
3768 catch {unset ancestor}
3769 set anc_todo [list $a]
3770 if {$highlight_related ne [mc "None"]} {
3776 proc rhighlight_none {} {
3779 catch {unset rhighlights}
3783 proc is_descendent {a} {
3784 global curview children descendent desc_todo
3787 set la [rowofcommit $a]
3791 for {set i 0} {$i < [llength $todo]} {incr i} {
3792 set do [lindex $todo $i]
3793 if {[rowofcommit $do] < $la} {
3794 lappend leftover $do
3797 foreach nk $children($v,$do) {
3798 if {![info exists descendent($nk)]} {
3799 set descendent($nk) 1
3807 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3811 set descendent($a) 0
3812 set desc_todo $leftover
3815 proc is_ancestor {a} {
3816 global curview parents ancestor anc_todo
3819 set la [rowofcommit $a]
3823 for {set i 0} {$i < [llength $todo]} {incr i} {
3824 set do [lindex $todo $i]
3825 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
3826 lappend leftover $do
3829 foreach np $parents($v,$do) {
3830 if {![info exists ancestor($np)]} {
3839 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3844 set anc_todo $leftover
3847 proc askrelhighlight {row id} {
3848 global descendent highlight_related iddrawn rhighlights
3849 global selectedline ancestor
3851 if {$selectedline eq {}} return
3853 if {$highlight_related eq [mc "Descendant"] ||
3854 $highlight_related eq [mc "Not descendant"]} {
3855 if {![info exists descendent($id)]} {
3858 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
3861 } elseif {$highlight_related eq [mc "Ancestor"] ||
3862 $highlight_related eq [mc "Not ancestor"]} {
3863 if {![info exists ancestor($id)]} {
3866 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
3870 if {[info exists iddrawn($id)]} {
3871 if {$isbold && ![ishighlighted $id]} {
3872 bolden $row mainfontbold
3875 set rhighlights($id) $isbold
3878 # Graph layout functions
3880 proc shortids {ids} {
3883 if {[llength $id] > 1} {
3884 lappend res [shortids $id]
3885 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
3886 lappend res [string range $id 0 7]
3897 for {set mask 1} {$mask <= $n} {incr mask $mask} {
3898 if {($n & $mask) != 0} {
3899 set ret [concat $ret $o]
3901 set o [concat $o $o]
3906 proc ordertoken {id} {
3907 global ordertok curview varcid varcstart varctok curview parents children
3908 global nullid nullid2
3910 if {[info exists ordertok($id)]} {
3911 return $ordertok($id)
3916 if {[info exists varcid($curview,$id)]} {
3917 set a $varcid($curview,$id)
3918 set p [lindex $varcstart($curview) $a]
3920 set p [lindex $children($curview,$id) 0]
3922 if {[info exists ordertok($p)]} {
3923 set tok $ordertok($p)
3926 set id [first_real_child $curview,$p]
3929 set tok [lindex $varctok($curview) $varcid($curview,$p)]
3932 if {[llength $parents($curview,$id)] == 1} {
3933 lappend todo [list $p {}]
3935 set j [lsearch -exact $parents($curview,$id) $p]
3937 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
3939 lappend todo [list $p [strrep $j]]
3942 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
3943 set p [lindex $todo $i 0]
3944 append tok [lindex $todo $i 1]
3945 set ordertok($p) $tok
3947 set ordertok($origid) $tok
3951 # Work out where id should go in idlist so that order-token
3952 # values increase from left to right
3953 proc idcol {idlist id {i 0}} {
3954 set t [ordertoken $id]
3958 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
3959 if {$i > [llength $idlist]} {
3960 set i [llength $idlist]
3962 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
3965 if {$t > [ordertoken [lindex $idlist $i]]} {
3966 while {[incr i] < [llength $idlist] &&
3967 $t >= [ordertoken [lindex $idlist $i]]} {}
3973 proc initlayout {} {
3974 global rowidlist rowisopt rowfinal displayorder parentlist
3975 global numcommits canvxmax canv
3977 global colormap rowtextx
3986 set canvxmax [$canv cget -width]
3987 catch {unset colormap}
3988 catch {unset rowtextx}
3992 proc setcanvscroll {} {
3993 global canv canv2 canv3 numcommits linespc canvxmax canvy0
3994 global lastscrollset lastscrollrows
3996 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
3997 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
3998 $canv2 conf -scrollregion [list 0 0 0 $ymax]
3999 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4000 set lastscrollset [clock clicks -milliseconds]
4001 set lastscrollrows $numcommits
4004 proc visiblerows {} {
4005 global canv numcommits linespc
4007 set ymax [lindex [$canv cget -scrollregion] 3]
4008 if {$ymax eq {} || $ymax == 0} return
4010 set y0 [expr {int([lindex $f 0] * $ymax)}]
4011 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4015 set y1 [expr {int([lindex $f 1] * $ymax)}]
4016 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4017 if {$r1 >= $numcommits} {
4018 set r1 [expr {$numcommits - 1}]
4020 return [list $r0 $r1]
4023 proc layoutmore {} {
4024 global commitidx viewcomplete curview
4025 global numcommits pending_select curview
4026 global lastscrollset lastscrollrows commitinterest
4028 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4029 [clock clicks -milliseconds] - $lastscrollset > 500} {
4032 if {[info exists pending_select] &&
4033 [commitinview $pending_select $curview]} {
4034 selectline [rowofcommit $pending_select] 1
4039 proc doshowlocalchanges {} {
4040 global curview mainheadid
4042 if {$mainheadid eq {}} return
4043 if {[commitinview $mainheadid $curview]} {
4046 lappend commitinterest($mainheadid) {dodiffindex}
4050 proc dohidelocalchanges {} {
4051 global nullid nullid2 lserial curview
4053 if {[commitinview $nullid $curview]} {
4054 removefakerow $nullid
4056 if {[commitinview $nullid2 $curview]} {
4057 removefakerow $nullid2
4062 # spawn off a process to do git diff-index --cached HEAD
4063 proc dodiffindex {} {
4064 global lserial showlocalchanges
4067 if {!$showlocalchanges || !$isworktree} return
4069 set fd [open "|git diff-index --cached HEAD" r]
4070 fconfigure $fd -blocking 0
4071 set i [reg_instance $fd]
4072 filerun $fd [list readdiffindex $fd $lserial $i]
4075 proc readdiffindex {fd serial inst} {
4076 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4079 if {[gets $fd line] < 0} {
4085 # we only need to see one line and we don't really care what it says...
4088 if {$serial != $lserial} {
4092 # now see if there are any local changes not checked in to the index
4093 set fd [open "|git diff-files" r]
4094 fconfigure $fd -blocking 0
4095 set i [reg_instance $fd]
4096 filerun $fd [list readdifffiles $fd $serial $i]
4098 if {$isdiff && ![commitinview $nullid2 $curview]} {
4099 # add the line for the changes in the index to the graph
4100 set hl [mc "Local changes checked in to index but not committed"]
4101 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4102 set commitdata($nullid2) "\n $hl\n"
4103 if {[commitinview $nullid $curview]} {
4104 removefakerow $nullid
4106 insertfakerow $nullid2 $mainheadid
4107 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4108 removefakerow $nullid2
4113 proc readdifffiles {fd serial inst} {
4114 global mainheadid nullid nullid2 curview
4115 global commitinfo commitdata lserial
4118 if {[gets $fd line] < 0} {
4124 # we only need to see one line and we don't really care what it says...
4127 if {$serial != $lserial} {
4131 if {$isdiff && ![commitinview $nullid $curview]} {
4132 # add the line for the local diff to the graph
4133 set hl [mc "Local uncommitted changes, not checked in to index"]
4134 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4135 set commitdata($nullid) "\n $hl\n"
4136 if {[commitinview $nullid2 $curview]} {
4141 insertfakerow $nullid $p
4142 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4143 removefakerow $nullid
4148 proc nextuse {id row} {
4149 global curview children
4151 if {[info exists children($curview,$id)]} {
4152 foreach kid $children($curview,$id) {
4153 if {![commitinview $kid $curview]} {
4156 if {[rowofcommit $kid] > $row} {
4157 return [rowofcommit $kid]
4161 if {[commitinview $id $curview]} {
4162 return [rowofcommit $id]
4167 proc prevuse {id row} {
4168 global curview children
4171 if {[info exists children($curview,$id)]} {
4172 foreach kid $children($curview,$id) {
4173 if {![commitinview $kid $curview]} break
4174 if {[rowofcommit $kid] < $row} {
4175 set ret [rowofcommit $kid]
4182 proc make_idlist {row} {
4183 global displayorder parentlist uparrowlen downarrowlen mingaplen
4184 global commitidx curview children
4186 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4190 set ra [expr {$row - $downarrowlen}]
4194 set rb [expr {$row + $uparrowlen}]
4195 if {$rb > $commitidx($curview)} {
4196 set rb $commitidx($curview)
4198 make_disporder $r [expr {$rb + 1}]
4200 for {} {$r < $ra} {incr r} {
4201 set nextid [lindex $displayorder [expr {$r + 1}]]
4202 foreach p [lindex $parentlist $r] {
4203 if {$p eq $nextid} continue
4204 set rn [nextuse $p $r]
4206 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4207 lappend ids [list [ordertoken $p] $p]
4211 for {} {$r < $row} {incr r} {
4212 set nextid [lindex $displayorder [expr {$r + 1}]]
4213 foreach p [lindex $parentlist $r] {
4214 if {$p eq $nextid} continue
4215 set rn [nextuse $p $r]
4216 if {$rn < 0 || $rn >= $row} {
4217 lappend ids [list [ordertoken $p] $p]
4221 set id [lindex $displayorder $row]
4222 lappend ids [list [ordertoken $id] $id]
4224 foreach p [lindex $parentlist $r] {
4225 set firstkid [lindex $children($curview,$p) 0]
4226 if {[rowofcommit $firstkid] < $row} {
4227 lappend ids [list [ordertoken $p] $p]
4231 set id [lindex $displayorder $r]
4233 set firstkid [lindex $children($curview,$id) 0]
4234 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4235 lappend ids [list [ordertoken $id] $id]
4240 foreach idx [lsort -unique $ids] {
4241 lappend idlist [lindex $idx 1]
4246 proc rowsequal {a b} {
4247 while {[set i [lsearch -exact $a {}]] >= 0} {
4248 set a [lreplace $a $i $i]
4250 while {[set i [lsearch -exact $b {}]] >= 0} {
4251 set b [lreplace $b $i $i]
4253 return [expr {$a eq $b}]
4256 proc makeupline {id row rend col} {
4257 global rowidlist uparrowlen downarrowlen mingaplen
4259 for {set r $rend} {1} {set r $rstart} {
4260 set rstart [prevuse $id $r]
4261 if {$rstart < 0} return
4262 if {$rstart < $row} break
4264 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4265 set rstart [expr {$rend - $uparrowlen - 1}]
4267 for {set r $rstart} {[incr r] <= $row} {} {
4268 set idlist [lindex $rowidlist $r]
4269 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4270 set col [idcol $idlist $id $col]
4271 lset rowidlist $r [linsert $idlist $col $id]
4277 proc layoutrows {row endrow} {
4278 global rowidlist rowisopt rowfinal displayorder
4279 global uparrowlen downarrowlen maxwidth mingaplen
4280 global children parentlist
4281 global commitidx viewcomplete curview
4283 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4286 set rm1 [expr {$row - 1}]
4287 foreach id [lindex $rowidlist $rm1] {
4292 set final [lindex $rowfinal $rm1]
4294 for {} {$row < $endrow} {incr row} {
4295 set rm1 [expr {$row - 1}]
4296 if {$rm1 < 0 || $idlist eq {}} {
4297 set idlist [make_idlist $row]
4300 set id [lindex $displayorder $rm1]
4301 set col [lsearch -exact $idlist $id]
4302 set idlist [lreplace $idlist $col $col]
4303 foreach p [lindex $parentlist $rm1] {
4304 if {[lsearch -exact $idlist $p] < 0} {
4305 set col [idcol $idlist $p $col]
4306 set idlist [linsert $idlist $col $p]
4307 # if not the first child, we have to insert a line going up
4308 if {$id ne [lindex $children($curview,$p) 0]} {
4309 makeupline $p $rm1 $row $col
4313 set id [lindex $displayorder $row]
4314 if {$row > $downarrowlen} {
4315 set termrow [expr {$row - $downarrowlen - 1}]
4316 foreach p [lindex $parentlist $termrow] {
4317 set i [lsearch -exact $idlist $p]
4318 if {$i < 0} continue
4319 set nr [nextuse $p $termrow]
4320 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4321 set idlist [lreplace $idlist $i $i]
4325 set col [lsearch -exact $idlist $id]
4327 set col [idcol $idlist $id]
4328 set idlist [linsert $idlist $col $id]
4329 if {$children($curview,$id) ne {}} {
4330 makeupline $id $rm1 $row $col
4333 set r [expr {$row + $uparrowlen - 1}]
4334 if {$r < $commitidx($curview)} {
4336 foreach p [lindex $parentlist $r] {
4337 if {[lsearch -exact $idlist $p] >= 0} continue
4338 set fk [lindex $children($curview,$p) 0]
4339 if {[rowofcommit $fk] < $row} {
4340 set x [idcol $idlist $p $x]
4341 set idlist [linsert $idlist $x $p]
4344 if {[incr r] < $commitidx($curview)} {
4345 set p [lindex $displayorder $r]
4346 if {[lsearch -exact $idlist $p] < 0} {
4347 set fk [lindex $children($curview,$p) 0]
4348 if {$fk ne {} && [rowofcommit $fk] < $row} {
4349 set x [idcol $idlist $p $x]
4350 set idlist [linsert $idlist $x $p]
4356 if {$final && !$viewcomplete($curview) &&
4357 $row + $uparrowlen + $mingaplen + $downarrowlen
4358 >= $commitidx($curview)} {
4361 set l [llength $rowidlist]
4363 lappend rowidlist $idlist
4365 lappend rowfinal $final
4366 } elseif {$row < $l} {
4367 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4368 lset rowidlist $row $idlist
4371 lset rowfinal $row $final
4373 set pad [ntimes [expr {$row - $l}] {}]
4374 set rowidlist [concat $rowidlist $pad]
4375 lappend rowidlist $idlist
4376 set rowfinal [concat $rowfinal $pad]
4377 lappend rowfinal $final
4378 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4384 proc changedrow {row} {
4385 global displayorder iddrawn rowisopt need_redisplay
4387 set l [llength $rowisopt]
4389 lset rowisopt $row 0
4390 if {$row + 1 < $l} {
4391 lset rowisopt [expr {$row + 1}] 0
4392 if {$row + 2 < $l} {
4393 lset rowisopt [expr {$row + 2}] 0
4397 set id [lindex $displayorder $row]
4398 if {[info exists iddrawn($id)]} {
4399 set need_redisplay 1
4403 proc insert_pad {row col npad} {
4406 set pad [ntimes $npad {}]
4407 set idlist [lindex $rowidlist $row]
4408 set bef [lrange $idlist 0 [expr {$col - 1}]]
4409 set aft [lrange $idlist $col end]
4410 set i [lsearch -exact $aft {}]
4412 set aft [lreplace $aft $i $i]
4414 lset rowidlist $row [concat $bef $pad $aft]
4418 proc optimize_rows {row col endrow} {
4419 global rowidlist rowisopt displayorder curview children
4424 for {} {$row < $endrow} {incr row; set col 0} {
4425 if {[lindex $rowisopt $row]} continue
4427 set y0 [expr {$row - 1}]
4428 set ym [expr {$row - 2}]
4429 set idlist [lindex $rowidlist $row]
4430 set previdlist [lindex $rowidlist $y0]
4431 if {$idlist eq {} || $previdlist eq {}} continue
4433 set pprevidlist [lindex $rowidlist $ym]
4434 if {$pprevidlist eq {}} continue
4440 for {} {$col < [llength $idlist]} {incr col} {
4441 set id [lindex $idlist $col]
4442 if {[lindex $previdlist $col] eq $id} continue
4447 set x0 [lsearch -exact $previdlist $id]
4448 if {$x0 < 0} continue
4449 set z [expr {$x0 - $col}]
4453 set xm [lsearch -exact $pprevidlist $id]
4455 set z0 [expr {$xm - $x0}]
4459 # if row y0 is the first child of $id then it's not an arrow
4460 if {[lindex $children($curview,$id) 0] ne
4461 [lindex $displayorder $y0]} {
4465 if {!$isarrow && $id ne [lindex $displayorder $row] &&
4466 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
4469 # Looking at lines from this row to the previous row,
4470 # make them go straight up if they end in an arrow on
4471 # the previous row; otherwise make them go straight up
4473 if {$z < -1 || ($z < 0 && $isarrow)} {
4474 # Line currently goes left too much;
4475 # insert pads in the previous row, then optimize it
4476 set npad [expr {-1 - $z + $isarrow}]
4477 insert_pad $y0 $x0 $npad
4479 optimize_rows $y0 $x0 $row
4481 set previdlist [lindex $rowidlist $y0]
4482 set x0 [lsearch -exact $previdlist $id]
4483 set z [expr {$x0 - $col}]
4485 set pprevidlist [lindex $rowidlist $ym]
4486 set xm [lsearch -exact $pprevidlist $id]
4487 set z0 [expr {$xm - $x0}]
4489 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
4490 # Line currently goes right too much;
4491 # insert pads in this line
4492 set npad [expr {$z - 1 + $isarrow}]
4493 insert_pad $row $col $npad
4494 set idlist [lindex $rowidlist $row]
4496 set z [expr {$x0 - $col}]
4499 if {$z0 eq {} && !$isarrow && $ym >= 0} {
4500 # this line links to its first child on row $row-2
4501 set id [lindex $displayorder $ym]
4502 set xc [lsearch -exact $pprevidlist $id]
4504 set z0 [expr {$xc - $x0}]
4507 # avoid lines jigging left then immediately right
4508 if {$z0 ne {} && $z < 0 && $z0 > 0} {
4509 insert_pad $y0 $x0 1
4511 optimize_rows $y0 $x0 $row
4512 set previdlist [lindex $rowidlist $y0]
4516 # Find the first column that doesn't have a line going right
4517 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
4518 set id [lindex $idlist $col]
4519 if {$id eq {}} break
4520 set x0 [lsearch -exact $previdlist $id]
4522 # check if this is the link to the first child
4523 set kid [lindex $displayorder $y0]
4524 if {[lindex $children($curview,$id) 0] eq $kid} {
4525 # it is, work out offset to child
4526 set x0 [lsearch -exact $previdlist $kid]
4529 if {$x0 <= $col} break
4531 # Insert a pad at that column as long as it has a line and
4532 # isn't the last column
4533 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
4534 set idlist [linsert $idlist $col {}]
4535 lset rowidlist $row $idlist
4543 global canvx0 linespc
4544 return [expr {$canvx0 + $col * $linespc}]
4548 global canvy0 linespc
4549 return [expr {$canvy0 + $row * $linespc}]
4552 proc linewidth {id} {
4553 global thickerline lthickness
4556 if {[info exists thickerline] && $id eq $thickerline} {
4557 set wid [expr {2 * $lthickness}]
4562 proc rowranges {id} {
4563 global curview children uparrowlen downarrowlen
4566 set kids $children($curview,$id)
4572 foreach child $kids {
4573 if {![commitinview $child $curview]} break
4574 set row [rowofcommit $child]
4575 if {![info exists prev]} {
4576 lappend ret [expr {$row + 1}]
4578 if {$row <= $prevrow} {
4579 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4581 # see if the line extends the whole way from prevrow to row
4582 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4583 [lsearch -exact [lindex $rowidlist \
4584 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4585 # it doesn't, see where it ends
4586 set r [expr {$prevrow + $downarrowlen}]
4587 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4588 while {[incr r -1] > $prevrow &&
4589 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4591 while {[incr r] <= $row &&
4592 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4596 # see where it starts up again
4597 set r [expr {$row - $uparrowlen}]
4598 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4599 while {[incr r] < $row &&
4600 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4602 while {[incr r -1] >= $prevrow &&
4603 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4609 if {$child eq $id} {
4618 proc drawlineseg {id row endrow arrowlow} {
4619 global rowidlist displayorder iddrawn linesegs
4620 global canv colormap linespc curview maxlinelen parentlist
4622 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4623 set le [expr {$row + 1}]
4626 set c [lsearch -exact [lindex $rowidlist $le] $id]
4632 set x [lindex $displayorder $le]
4637 if {[info exists iddrawn($x)] || $le == $endrow} {
4638 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
4654 if {[info exists linesegs($id)]} {
4655 set lines $linesegs($id)
4657 set r0 [lindex $li 0]
4659 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
4669 set li [lindex $lines [expr {$i-1}]]
4670 set r1 [lindex $li 1]
4671 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
4676 set x [lindex $cols [expr {$le - $row}]]
4677 set xp [lindex $cols [expr {$le - 1 - $row}]]
4678 set dir [expr {$xp - $x}]
4680 set ith [lindex $lines $i 2]
4681 set coords [$canv coords $ith]
4682 set ah [$canv itemcget $ith -arrow]
4683 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
4684 set x2 [lindex $cols [expr {$le + 1 - $row}]]
4685 if {$x2 ne {} && $x - $x2 == $dir} {
4686 set coords [lrange $coords 0 end-2]
4689 set coords [list [xc $le $x] [yc $le]]
4692 set itl [lindex $lines [expr {$i-1}] 2]
4693 set al [$canv itemcget $itl -arrow]
4694 set arrowlow [expr {$al eq "last" || $al eq "both"}]
4695 } elseif {$arrowlow} {
4696 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
4697 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
4701 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
4702 for {set y $le} {[incr y -1] > $row} {} {
4704 set xp [lindex $cols [expr {$y - 1 - $row}]]
4705 set ndir [expr {$xp - $x}]
4706 if {$dir != $ndir || $xp < 0} {
4707 lappend coords [xc $y $x] [yc $y]
4713 # join parent line to first child
4714 set ch [lindex $displayorder $row]
4715 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
4717 puts "oops: drawlineseg: child $ch not on row $row"
4718 } elseif {$xc != $x} {
4719 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
4720 set d [expr {int(0.5 * $linespc)}]
4723 set x2 [expr {$x1 - $d}]
4725 set x2 [expr {$x1 + $d}]
4728 set y1 [expr {$y2 + $d}]
4729 lappend coords $x1 $y1 $x2 $y2
4730 } elseif {$xc < $x - 1} {
4731 lappend coords [xc $row [expr {$x-1}]] [yc $row]
4732 } elseif {$xc > $x + 1} {
4733 lappend coords [xc $row [expr {$x+1}]] [yc $row]
4737 lappend coords [xc $row $x] [yc $row]
4739 set xn [xc $row $xp]
4741 lappend coords $xn $yn
4745 set t [$canv create line $coords -width [linewidth $id] \
4746 -fill $colormap($id) -tags lines.$id -arrow $arrow]
4749 set lines [linsert $lines $i [list $row $le $t]]
4751 $canv coords $ith $coords
4752 if {$arrow ne $ah} {
4753 $canv itemconf $ith -arrow $arrow
4755 lset lines $i 0 $row
4758 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
4759 set ndir [expr {$xo - $xp}]
4760 set clow [$canv coords $itl]
4761 if {$dir == $ndir} {
4762 set clow [lrange $clow 2 end]
4764 set coords [concat $coords $clow]
4766 lset lines [expr {$i-1}] 1 $le
4768 # coalesce two pieces
4770 set b [lindex $lines [expr {$i-1}] 0]
4771 set e [lindex $lines $i 1]
4772 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
4774 $canv coords $itl $coords
4775 if {$arrow ne $al} {
4776 $canv itemconf $itl -arrow $arrow
4780 set linesegs($id) $lines
4784 proc drawparentlinks {id row} {
4785 global rowidlist canv colormap curview parentlist
4786 global idpos linespc
4788 set rowids [lindex $rowidlist $row]
4789 set col [lsearch -exact $rowids $id]
4790 if {$col < 0} return
4791 set olds [lindex $parentlist $row]
4792 set row2 [expr {$row + 1}]
4793 set x [xc $row $col]
4796 set d [expr {int(0.5 * $linespc)}]
4797 set ymid [expr {$y + $d}]
4798 set ids [lindex $rowidlist $row2]
4799 # rmx = right-most X coord used
4802 set i [lsearch -exact $ids $p]
4804 puts "oops, parent $p of $id not in list"
4807 set x2 [xc $row2 $i]
4811 set j [lsearch -exact $rowids $p]
4813 # drawlineseg will do this one for us
4817 # should handle duplicated parents here...
4818 set coords [list $x $y]
4820 # if attaching to a vertical segment, draw a smaller
4821 # slant for visual distinctness
4824 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
4826 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
4828 } elseif {$i < $col && $i < $j} {
4829 # segment slants towards us already
4830 lappend coords [xc $row $j] $y
4832 if {$i < $col - 1} {
4833 lappend coords [expr {$x2 + $linespc}] $y
4834 } elseif {$i > $col + 1} {
4835 lappend coords [expr {$x2 - $linespc}] $y
4837 lappend coords $x2 $y2
4840 lappend coords $x2 $y2
4842 set t [$canv create line $coords -width [linewidth $p] \
4843 -fill $colormap($p) -tags lines.$p]
4847 if {$rmx > [lindex $idpos($id) 1]} {
4848 lset idpos($id) 1 $rmx
4853 proc drawlines {id} {
4856 $canv itemconf lines.$id -width [linewidth $id]
4859 proc drawcmittext {id row col} {
4860 global linespc canv canv2 canv3 fgcolor curview
4861 global cmitlisted commitinfo rowidlist parentlist
4862 global rowtextx idpos idtags idheads idotherrefs
4863 global linehtag linentag linedtag selectedline
4864 global canvxmax boldrows boldnamerows fgcolor
4865 global mainheadid nullid nullid2 circleitem circlecolors
4867 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
4868 set listed $cmitlisted($curview,$id)
4869 if {$id eq $nullid} {
4871 } elseif {$id eq $nullid2} {
4873 } elseif {$id eq $mainheadid} {
4876 set ofill [lindex $circlecolors $listed]
4878 set x [xc $row $col]
4880 set orad [expr {$linespc / 3}]
4882 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
4883 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4884 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4885 } elseif {$listed == 3} {
4886 # triangle pointing left for left-side commits
4887 set t [$canv create polygon \
4888 [expr {$x - $orad}] $y \
4889 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
4890 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4891 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4893 # triangle pointing right for right-side commits
4894 set t [$canv create polygon \
4895 [expr {$x + $orad - 1}] $y \
4896 [expr {$x - $orad}] [expr {$y - $orad}] \
4897 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
4898 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4900 set circleitem($row) $t
4902 $canv bind $t <1> {selcanvline {} %x %y}
4903 set rmx [llength [lindex $rowidlist $row]]
4904 set olds [lindex $parentlist $row]
4906 set nextids [lindex $rowidlist [expr {$row + 1}]]
4908 set i [lsearch -exact $nextids $p]
4914 set xt [xc $row $rmx]
4915 set rowtextx($row) $xt
4916 set idpos($id) [list $x $xt $y]
4917 if {[info exists idtags($id)] || [info exists idheads($id)]
4918 || [info exists idotherrefs($id)]} {
4919 set xt [drawtags $id $x $xt $y]
4921 set headline [lindex $commitinfo($id) 0]
4922 set name [lindex $commitinfo($id) 1]
4923 set date [lindex $commitinfo($id) 2]
4924 set date [formatdate $date]
4927 set isbold [ishighlighted $id]
4929 lappend boldrows $row
4930 set font mainfontbold
4932 lappend boldnamerows $row
4933 set nfont mainfontbold
4936 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
4937 -text $headline -font $font -tags text]
4938 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
4939 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
4940 -text $name -font $nfont -tags text]
4941 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
4942 -text $date -font mainfont -tags text]
4943 if {$selectedline == $row} {
4946 set xr [expr {$xt + [font measure $font $headline]}]
4947 if {$xr > $canvxmax} {
4953 proc drawcmitrow {row} {
4954 global displayorder rowidlist nrows_drawn
4955 global iddrawn markingmatches
4956 global commitinfo numcommits
4957 global filehighlight fhighlights findpattern nhighlights
4958 global hlview vhighlights
4959 global highlight_related rhighlights
4961 if {$row >= $numcommits} return
4963 set id [lindex $displayorder $row]
4964 if {[info exists hlview] && ![info exists vhighlights($id)]} {
4965 askvhighlight $row $id
4967 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
4968 askfilehighlight $row $id
4970 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
4971 askfindhighlight $row $id
4973 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
4974 askrelhighlight $row $id
4976 if {![info exists iddrawn($id)]} {
4977 set col [lsearch -exact [lindex $rowidlist $row] $id]
4979 puts "oops, row $row id $id not in list"
4982 if {![info exists commitinfo($id)]} {
4986 drawcmittext $id $row $col
4990 if {$markingmatches} {
4991 markrowmatches $row $id
4995 proc drawcommits {row {endrow {}}} {
4996 global numcommits iddrawn displayorder curview need_redisplay
4997 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5002 if {$endrow eq {}} {
5005 if {$endrow >= $numcommits} {
5006 set endrow [expr {$numcommits - 1}]
5009 set rl1 [expr {$row - $downarrowlen - 3}]
5013 set ro1 [expr {$row - 3}]
5017 set r2 [expr {$endrow + $uparrowlen + 3}]
5018 if {$r2 > $numcommits} {
5021 for {set r $rl1} {$r < $r2} {incr r} {
5022 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5026 set rl1 [expr {$r + 1}]
5032 optimize_rows $ro1 0 $r2
5033 if {$need_redisplay || $nrows_drawn > 2000} {
5038 # make the lines join to already-drawn rows either side
5039 set r [expr {$row - 1}]
5040 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5043 set er [expr {$endrow + 1}]
5044 if {$er >= $numcommits ||
5045 ![info exists iddrawn([lindex $displayorder $er])]} {
5048 for {} {$r <= $er} {incr r} {
5049 set id [lindex $displayorder $r]
5050 set wasdrawn [info exists iddrawn($id)]
5052 if {$r == $er} break
5053 set nextid [lindex $displayorder [expr {$r + 1}]]
5054 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5055 drawparentlinks $id $r
5057 set rowids [lindex $rowidlist $r]
5058 foreach lid $rowids {
5059 if {$lid eq {}} continue
5060 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5062 # see if this is the first child of any of its parents
5063 foreach p [lindex $parentlist $r] {
5064 if {[lsearch -exact $rowids $p] < 0} {
5065 # make this line extend up to the child
5066 set lineend($p) [drawlineseg $p $r $er 0]
5070 set lineend($lid) [drawlineseg $lid $r $er 1]
5076 proc undolayout {row} {
5077 global uparrowlen mingaplen downarrowlen
5078 global rowidlist rowisopt rowfinal need_redisplay
5080 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5084 if {[llength $rowidlist] > $r} {
5086 set rowidlist [lrange $rowidlist 0 $r]
5087 set rowfinal [lrange $rowfinal 0 $r]
5088 set rowisopt [lrange $rowisopt 0 $r]
5089 set need_redisplay 1
5094 proc drawvisible {} {
5095 global canv linespc curview vrowmod selectedline targetrow targetid
5096 global need_redisplay cscroll numcommits
5098 set fs [$canv yview]
5099 set ymax [lindex [$canv cget -scrollregion] 3]
5100 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5101 set f0 [lindex $fs 0]
5102 set f1 [lindex $fs 1]
5103 set y0 [expr {int($f0 * $ymax)}]
5104 set y1 [expr {int($f1 * $ymax)}]
5106 if {[info exists targetid]} {
5107 if {[commitinview $targetid $curview]} {
5108 set r [rowofcommit $targetid]
5109 if {$r != $targetrow} {
5110 # Fix up the scrollregion and change the scrolling position
5111 # now that our target row has moved.
5112 set diff [expr {($r - $targetrow) * $linespc}]
5115 set ymax [lindex [$canv cget -scrollregion] 3]
5118 set f0 [expr {$y0 / $ymax}]
5119 set f1 [expr {$y1 / $ymax}]
5120 allcanvs yview moveto $f0
5121 $cscroll set $f0 $f1
5122 set need_redisplay 1
5129 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5130 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5131 if {$endrow >= $vrowmod($curview)} {
5132 update_arcrows $curview
5134 if {$selectedline ne {} &&
5135 $row <= $selectedline && $selectedline <= $endrow} {
5136 set targetrow $selectedline
5137 } elseif {[info exists targetid]} {
5138 set targetrow [expr {int(($row + $endrow) / 2)}]
5140 if {[info exists targetrow]} {
5141 if {$targetrow >= $numcommits} {
5142 set targetrow [expr {$numcommits - 1}]
5144 set targetid [commitonrow $targetrow]
5146 drawcommits $row $endrow
5149 proc clear_display {} {
5150 global iddrawn linesegs need_redisplay nrows_drawn
5151 global vhighlights fhighlights nhighlights rhighlights
5152 global linehtag linentag linedtag boldrows boldnamerows
5155 catch {unset iddrawn}
5156 catch {unset linesegs}
5157 catch {unset linehtag}
5158 catch {unset linentag}
5159 catch {unset linedtag}
5162 catch {unset vhighlights}
5163 catch {unset fhighlights}
5164 catch {unset nhighlights}
5165 catch {unset rhighlights}
5166 set need_redisplay 0
5170 proc findcrossings {id} {
5171 global rowidlist parentlist numcommits displayorder
5175 foreach {s e} [rowranges $id] {
5176 if {$e >= $numcommits} {
5177 set e [expr {$numcommits - 1}]
5179 if {$e <= $s} continue
5180 for {set row $e} {[incr row -1] >= $s} {} {
5181 set x [lsearch -exact [lindex $rowidlist $row] $id]
5183 set olds [lindex $parentlist $row]
5184 set kid [lindex $displayorder $row]
5185 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5186 if {$kidx < 0} continue
5187 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5189 set px [lsearch -exact $nextrow $p]
5190 if {$px < 0} continue
5191 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5192 if {[lsearch -exact $ccross $p] >= 0} continue
5193 if {$x == $px + ($kidx < $px? -1: 1)} {
5195 } elseif {[lsearch -exact $cross $p] < 0} {
5202 return [concat $ccross {{}} $cross]
5205 proc assigncolor {id} {
5206 global colormap colors nextcolor
5207 global parents children children curview
5209 if {[info exists colormap($id)]} return
5210 set ncolors [llength $colors]
5211 if {[info exists children($curview,$id)]} {
5212 set kids $children($curview,$id)
5216 if {[llength $kids] == 1} {
5217 set child [lindex $kids 0]
5218 if {[info exists colormap($child)]
5219 && [llength $parents($curview,$child)] == 1} {
5220 set colormap($id) $colormap($child)
5226 foreach x [findcrossings $id] {
5228 # delimiter between corner crossings and other crossings
5229 if {[llength $badcolors] >= $ncolors - 1} break
5230 set origbad $badcolors
5232 if {[info exists colormap($x)]
5233 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5234 lappend badcolors $colormap($x)
5237 if {[llength $badcolors] >= $ncolors} {
5238 set badcolors $origbad
5240 set origbad $badcolors
5241 if {[llength $badcolors] < $ncolors - 1} {
5242 foreach child $kids {
5243 if {[info exists colormap($child)]
5244 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5245 lappend badcolors $colormap($child)
5247 foreach p $parents($curview,$child) {
5248 if {[info exists colormap($p)]
5249 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5250 lappend badcolors $colormap($p)
5254 if {[llength $badcolors] >= $ncolors} {
5255 set badcolors $origbad
5258 for {set i 0} {$i <= $ncolors} {incr i} {
5259 set c [lindex $colors $nextcolor]
5260 if {[incr nextcolor] >= $ncolors} {
5263 if {[lsearch -exact $badcolors $c]} break
5265 set colormap($id) $c
5268 proc bindline {t id} {
5271 $canv bind $t <Enter> "lineenter %x %y $id"
5272 $canv bind $t <Motion> "linemotion %x %y $id"
5273 $canv bind $t <Leave> "lineleave $id"
5274 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5277 proc drawtags {id x xt y1} {
5278 global idtags idheads idotherrefs mainhead
5279 global linespc lthickness
5280 global canv rowtextx curview fgcolor bgcolor
5285 if {[info exists idtags($id)]} {
5286 set marks $idtags($id)
5287 set ntags [llength $marks]
5289 if {[info exists idheads($id)]} {
5290 set marks [concat $marks $idheads($id)]
5291 set nheads [llength $idheads($id)]
5293 if {[info exists idotherrefs($id)]} {
5294 set marks [concat $marks $idotherrefs($id)]
5300 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5301 set yt [expr {$y1 - 0.5 * $linespc}]
5302 set yb [expr {$yt + $linespc - 1}]
5306 foreach tag $marks {
5308 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5309 set wid [font measure mainfontbold $tag]
5311 set wid [font measure mainfont $tag]
5315 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5317 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5318 -width $lthickness -fill black -tags tag.$id]
5320 foreach tag $marks x $xvals wid $wvals {
5321 set xl [expr {$x + $delta}]
5322 set xr [expr {$x + $delta + $wid + $lthickness}]
5324 if {[incr ntags -1] >= 0} {
5326 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5327 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5328 -width 1 -outline black -fill yellow -tags tag.$id]
5329 $canv bind $t <1> [list showtag $tag 1]
5330 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5332 # draw a head or other ref
5333 if {[incr nheads -1] >= 0} {
5335 if {$tag eq $mainhead} {
5336 set font mainfontbold
5341 set xl [expr {$xl - $delta/2}]
5342 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5343 -width 1 -outline black -fill $col -tags tag.$id
5344 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5345 set rwid [font measure mainfont $remoteprefix]
5346 set xi [expr {$x + 1}]
5347 set yti [expr {$yt + 1}]
5348 set xri [expr {$x + $rwid}]
5349 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5350 -width 0 -fill "#ffddaa" -tags tag.$id
5353 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5354 -font $font -tags [list tag.$id text]]
5356 $canv bind $t <1> [list showtag $tag 1]
5357 } elseif {$nheads >= 0} {
5358 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
5364 proc xcoord {i level ln} {
5365 global canvx0 xspc1 xspc2
5367 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5368 if {$i > 0 && $i == $level} {
5369 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5370 } elseif {$i > $level} {
5371 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5376 proc show_status {msg} {
5380 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5381 -tags text -fill $fgcolor
5384 # Don't change the text pane cursor if it is currently the hand cursor,
5385 # showing that we are over a sha1 ID link.
5386 proc settextcursor {c} {
5387 global ctext curtextcursor
5389 if {[$ctext cget -cursor] == $curtextcursor} {
5390 $ctext config -cursor $c
5392 set curtextcursor $c
5395 proc nowbusy {what {name {}}} {
5396 global isbusy busyname statusw
5398 if {[array names isbusy] eq {}} {
5399 . config -cursor watch
5403 set busyname($what) $name
5405 $statusw conf -text $name
5409 proc notbusy {what} {
5410 global isbusy maincursor textcursor busyname statusw
5414 if {$busyname($what) ne {} &&
5415 [$statusw cget -text] eq $busyname($what)} {
5416 $statusw conf -text {}
5419 if {[array names isbusy] eq {}} {
5420 . config -cursor $maincursor
5421 settextcursor $textcursor
5425 proc findmatches {f} {
5426 global findtype findstring
5427 if {$findtype == [mc "Regexp"]} {
5428 set matches [regexp -indices -all -inline $findstring $f]
5431 if {$findtype == [mc "IgnCase"]} {
5432 set f [string tolower $f]
5433 set fs [string tolower $fs]
5437 set l [string length $fs]
5438 while {[set j [string first $fs $f $i]] >= 0} {
5439 lappend matches [list $j [expr {$j+$l-1}]]
5440 set i [expr {$j + $l}]
5446 proc dofind {{dirn 1} {wrap 1}} {
5447 global findstring findstartline findcurline selectedline numcommits
5448 global gdttype filehighlight fh_serial find_dirn findallowwrap
5450 if {[info exists find_dirn]} {
5451 if {$find_dirn == $dirn} return
5455 if {$findstring eq {} || $numcommits == 0} return
5456 if {$selectedline eq {}} {
5457 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
5459 set findstartline $selectedline
5461 set findcurline $findstartline
5462 nowbusy finding [mc "Searching"]
5463 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
5464 after cancel do_file_hl $fh_serial
5465 do_file_hl $fh_serial
5468 set findallowwrap $wrap
5472 proc stopfinding {} {
5473 global find_dirn findcurline fprogcoord
5475 if {[info exists find_dirn]} {
5485 global commitdata commitinfo numcommits findpattern findloc
5486 global findstartline findcurline findallowwrap
5487 global find_dirn gdttype fhighlights fprogcoord
5488 global curview varcorder vrownum varccommits vrowmod
5490 if {![info exists find_dirn]} {
5493 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
5496 if {$find_dirn > 0} {
5498 if {$l >= $numcommits} {
5501 if {$l <= $findstartline} {
5502 set lim [expr {$findstartline + 1}]
5505 set moretodo $findallowwrap
5512 if {$l >= $findstartline} {
5513 set lim [expr {$findstartline - 1}]
5516 set moretodo $findallowwrap
5519 set n [expr {($lim - $l) * $find_dirn}]
5524 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
5525 update_arcrows $curview
5529 set ai [bsearch $vrownum($curview) $l]
5530 set a [lindex $varcorder($curview) $ai]
5531 set arow [lindex $vrownum($curview) $ai]
5532 set ids [lindex $varccommits($curview,$a)]
5533 set arowend [expr {$arow + [llength $ids]}]
5534 if {$gdttype eq [mc "containing:"]} {
5535 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5536 if {$l < $arow || $l >= $arowend} {
5538 set a [lindex $varcorder($curview) $ai]
5539 set arow [lindex $vrownum($curview) $ai]
5540 set ids [lindex $varccommits($curview,$a)]
5541 set arowend [expr {$arow + [llength $ids]}]
5543 set id [lindex $ids [expr {$l - $arow}]]
5544 # shouldn't happen unless git log doesn't give all the commits...
5545 if {![info exists commitdata($id)] ||
5546 ![doesmatch $commitdata($id)]} {
5549 if {![info exists commitinfo($id)]} {
5552 set info $commitinfo($id)
5553 foreach f $info ty $fldtypes {
5554 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
5563 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5564 if {$l < $arow || $l >= $arowend} {
5566 set a [lindex $varcorder($curview) $ai]
5567 set arow [lindex $vrownum($curview) $ai]
5568 set ids [lindex $varccommits($curview,$a)]
5569 set arowend [expr {$arow + [llength $ids]}]
5571 set id [lindex $ids [expr {$l - $arow}]]
5572 if {![info exists fhighlights($id)]} {
5573 # this sets fhighlights($id) to -1
5574 askfilehighlight $l $id
5576 if {$fhighlights($id) > 0} {
5580 if {$fhighlights($id) < 0} {
5583 set findcurline [expr {$l - $find_dirn}]
5588 if {$found || ($domore && !$moretodo)} {
5604 set findcurline [expr {$l - $find_dirn}]
5606 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
5610 set fprogcoord [expr {$n * 1.0 / $numcommits}]
5615 proc findselectline {l} {
5616 global findloc commentend ctext findcurline markingmatches gdttype
5618 set markingmatches 1
5621 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
5622 # highlight the matches in the comments
5623 set f [$ctext get 1.0 $commentend]
5624 set matches [findmatches $f]
5625 foreach match $matches {
5626 set start [lindex $match 0]
5627 set end [expr {[lindex $match 1] + 1}]
5628 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5634 # mark the bits of a headline or author that match a find string
5635 proc markmatches {canv l str tag matches font row} {
5638 set bbox [$canv bbox $tag]
5639 set x0 [lindex $bbox 0]
5640 set y0 [lindex $bbox 1]
5641 set y1 [lindex $bbox 3]
5642 foreach match $matches {
5643 set start [lindex $match 0]
5644 set end [lindex $match 1]
5645 if {$start > $end} continue
5646 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
5647 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
5648 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
5649 [expr {$x0+$xlen+2}] $y1 \
5650 -outline {} -tags [list match$l matches] -fill yellow]
5652 if {$row == $selectedline} {
5653 $canv raise $t secsel
5658 proc unmarkmatches {} {
5659 global markingmatches
5661 allcanvs delete matches
5662 set markingmatches 0
5666 proc selcanvline {w x y} {
5667 global canv canvy0 ctext linespc
5669 set ymax [lindex [$canv cget -scrollregion] 3]
5670 if {$ymax == {}} return
5671 set yfrac [lindex [$canv yview] 0]
5672 set y [expr {$y + $yfrac * $ymax}]
5673 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
5678 set xmax [lindex [$canv cget -scrollregion] 2]
5679 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
5680 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
5686 proc commit_descriptor {p} {
5688 if {![info exists commitinfo($p)]} {
5692 if {[llength $commitinfo($p)] > 1} {
5693 set l [lindex $commitinfo($p) 0]
5698 # append some text to the ctext widget, and make any SHA1 ID
5699 # that we know about be a clickable link.
5700 proc appendwithlinks {text tags} {
5701 global ctext linknum curview pendinglinks
5703 set start [$ctext index "end - 1c"]
5704 $ctext insert end $text $tags
5705 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
5709 set linkid [string range $text $s $e]
5711 $ctext tag delete link$linknum
5712 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
5713 setlink $linkid link$linknum
5718 proc setlink {id lk} {
5719 global curview ctext pendinglinks commitinterest
5721 if {[commitinview $id $curview]} {
5722 $ctext tag conf $lk -foreground blue -underline 1
5723 $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1]
5724 $ctext tag bind $lk <Enter> {linkcursor %W 1}
5725 $ctext tag bind $lk <Leave> {linkcursor %W -1}
5727 lappend pendinglinks($id) $lk
5728 lappend commitinterest($id) {makelink %I}
5732 proc makelink {id} {
5735 if {![info exists pendinglinks($id)]} return
5736 foreach lk $pendinglinks($id) {
5739 unset pendinglinks($id)
5742 proc linkcursor {w inc} {
5743 global linkentercount curtextcursor
5745 if {[incr linkentercount $inc] > 0} {
5746 $w configure -cursor hand2
5748 $w configure -cursor $curtextcursor
5749 if {$linkentercount < 0} {
5750 set linkentercount 0
5755 proc viewnextline {dir} {
5759 set ymax [lindex [$canv cget -scrollregion] 3]
5760 set wnow [$canv yview]
5761 set wtop [expr {[lindex $wnow 0] * $ymax}]
5762 set newtop [expr {$wtop + $dir * $linespc}]
5765 } elseif {$newtop > $ymax} {
5768 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5771 # add a list of tag or branch names at position pos
5772 # returns the number of names inserted
5773 proc appendrefs {pos ids var} {
5774 global ctext linknum curview $var maxrefs
5776 if {[catch {$ctext index $pos}]} {
5779 $ctext conf -state normal
5780 $ctext delete $pos "$pos lineend"
5783 foreach tag [set $var\($id\)] {
5784 lappend tags [list $tag $id]
5787 if {[llength $tags] > $maxrefs} {
5788 $ctext insert $pos "many ([llength $tags])"
5790 set tags [lsort -index 0 -decreasing $tags]
5793 set id [lindex $ti 1]
5796 $ctext tag delete $lk
5797 $ctext insert $pos $sep
5798 $ctext insert $pos [lindex $ti 0] $lk
5803 $ctext conf -state disabled
5804 return [llength $tags]
5807 # called when we have finished computing the nearby tags
5808 proc dispneartags {delay} {
5809 global selectedline currentid showneartags tagphase
5811 if {$selectedline eq {} || !$showneartags} return
5812 after cancel dispnexttag
5814 after 200 dispnexttag
5817 after idle dispnexttag
5822 proc dispnexttag {} {
5823 global selectedline currentid showneartags tagphase ctext
5825 if {$selectedline eq {} || !$showneartags} return
5826 switch -- $tagphase {
5828 set dtags [desctags $currentid]
5830 appendrefs precedes $dtags idtags
5834 set atags [anctags $currentid]
5836 appendrefs follows $atags idtags
5840 set dheads [descheads $currentid]
5841 if {$dheads ne {}} {
5842 if {[appendrefs branch $dheads idheads] > 1
5843 && [$ctext get "branch -3c"] eq "h"} {
5844 # turn "Branch" into "Branches"
5845 $ctext conf -state normal
5846 $ctext insert "branch -2c" "es"
5847 $ctext conf -state disabled
5852 if {[incr tagphase] <= 2} {
5853 after idle dispnexttag
5857 proc make_secsel {l} {
5858 global linehtag linentag linedtag canv canv2 canv3
5860 if {![info exists linehtag($l)]} return
5862 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
5863 -tags secsel -fill [$canv cget -selectbackground]]
5865 $canv2 delete secsel
5866 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
5867 -tags secsel -fill [$canv2 cget -selectbackground]]
5869 $canv3 delete secsel
5870 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
5871 -tags secsel -fill [$canv3 cget -selectbackground]]
5875 proc selectline {l isnew} {
5876 global canv ctext commitinfo selectedline
5877 global canvy0 linespc parents children curview
5878 global currentid sha1entry
5879 global commentend idtags linknum
5880 global mergemax numcommits pending_select
5881 global cmitmode showneartags allcommits
5882 global targetrow targetid lastscrollrows
5885 catch {unset pending_select}
5890 if {$l < 0 || $l >= $numcommits} return
5891 set id [commitonrow $l]
5896 if {$lastscrollrows < $numcommits} {
5900 set y [expr {$canvy0 + $l * $linespc}]
5901 set ymax [lindex [$canv cget -scrollregion] 3]
5902 set ytop [expr {$y - $linespc - 1}]
5903 set ybot [expr {$y + $linespc + 1}]
5904 set wnow [$canv yview]
5905 set wtop [expr {[lindex $wnow 0] * $ymax}]
5906 set wbot [expr {[lindex $wnow 1] * $ymax}]
5907 set wh [expr {$wbot - $wtop}]
5909 if {$ytop < $wtop} {
5910 if {$ybot < $wtop} {
5911 set newtop [expr {$y - $wh / 2.0}]
5914 if {$newtop > $wtop - $linespc} {
5915 set newtop [expr {$wtop - $linespc}]
5918 } elseif {$ybot > $wbot} {
5919 if {$ytop > $wbot} {
5920 set newtop [expr {$y - $wh / 2.0}]
5922 set newtop [expr {$ybot - $wh}]
5923 if {$newtop < $wtop + $linespc} {
5924 set newtop [expr {$wtop + $linespc}]
5928 if {$newtop != $wtop} {
5932 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5939 addtohistory [list selbyid $id]
5942 $sha1entry delete 0 end
5943 $sha1entry insert 0 $id
5945 $sha1entry selection from 0
5946 $sha1entry selection to end
5950 $ctext conf -state normal
5953 if {![info exists commitinfo($id)]} {
5956 set info $commitinfo($id)
5957 set date [formatdate [lindex $info 2]]
5958 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
5959 set date [formatdate [lindex $info 4]]
5960 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
5961 if {[info exists idtags($id)]} {
5962 $ctext insert end [mc "Tags:"]
5963 foreach tag $idtags($id) {
5964 $ctext insert end " $tag"
5966 $ctext insert end "\n"
5970 set olds $parents($curview,$id)
5971 if {[llength $olds] > 1} {
5974 if {$np >= $mergemax} {
5979 $ctext insert end "[mc "Parent"]: " $tag
5980 appendwithlinks [commit_descriptor $p] {}
5985 append headers "[mc "Parent"]: [commit_descriptor $p]"
5989 foreach c $children($curview,$id) {
5990 append headers "[mc "Child"]: [commit_descriptor $c]"
5993 # make anything that looks like a SHA1 ID be a clickable link
5994 appendwithlinks $headers {}
5995 if {$showneartags} {
5996 if {![info exists allcommits]} {
5999 $ctext insert end "[mc "Branch"]: "
6000 $ctext mark set branch "end -1c"
6001 $ctext mark gravity branch left
6002 $ctext insert end "\n[mc "Follows"]: "
6003 $ctext mark set follows "end -1c"
6004 $ctext mark gravity follows left
6005 $ctext insert end "\n[mc "Precedes"]: "
6006 $ctext mark set precedes "end -1c"
6007 $ctext mark gravity precedes left
6008 $ctext insert end "\n"
6011 $ctext insert end "\n"
6012 set comment [lindex $info 5]
6013 if {[string first "\r" $comment] >= 0} {
6014 set comment [string map {"\r" "\n "} $comment]
6016 appendwithlinks $comment {comment}
6018 $ctext tag remove found 1.0 end
6019 $ctext conf -state disabled
6020 set commentend [$ctext index "end - 1c"]
6022 init_flist [mc "Comments"]
6023 if {$cmitmode eq "tree"} {
6025 } elseif {[llength $olds] <= 1} {
6032 proc selfirstline {} {
6037 proc sellastline {} {
6040 set l [expr {$numcommits - 1}]
6044 proc selnextline {dir} {
6047 if {$selectedline eq {}} return
6048 set l [expr {$selectedline + $dir}]
6053 proc selnextpage {dir} {
6054 global canv linespc selectedline numcommits
6056 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6060 allcanvs yview scroll [expr {$dir * $lpp}] units
6062 if {$selectedline eq {}} return
6063 set l [expr {$selectedline + $dir * $lpp}]
6066 } elseif {$l >= $numcommits} {
6067 set l [expr $numcommits - 1]
6073 proc unselectline {} {
6074 global selectedline currentid
6077 catch {unset currentid}
6078 allcanvs delete secsel
6082 proc reselectline {} {
6085 if {$selectedline ne {}} {
6086 selectline $selectedline 0
6090 proc addtohistory {cmd} {
6091 global history historyindex curview
6093 set elt [list $curview $cmd]
6094 if {$historyindex > 0
6095 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6099 if {$historyindex < [llength $history]} {
6100 set history [lreplace $history $historyindex end $elt]
6102 lappend history $elt
6105 if {$historyindex > 1} {
6106 .tf.bar.leftbut conf -state normal
6108 .tf.bar.leftbut conf -state disabled
6110 .tf.bar.rightbut conf -state disabled
6116 set view [lindex $elt 0]
6117 set cmd [lindex $elt 1]
6118 if {$curview != $view} {
6125 global history historyindex
6128 if {$historyindex > 1} {
6129 incr historyindex -1
6130 godo [lindex $history [expr {$historyindex - 1}]]
6131 .tf.bar.rightbut conf -state normal
6133 if {$historyindex <= 1} {
6134 .tf.bar.leftbut conf -state disabled
6139 global history historyindex
6142 if {$historyindex < [llength $history]} {
6143 set cmd [lindex $history $historyindex]
6146 .tf.bar.leftbut conf -state normal
6148 if {$historyindex >= [llength $history]} {
6149 .tf.bar.rightbut conf -state disabled
6154 global treefilelist treeidlist diffids diffmergeid treepending
6155 global nullid nullid2
6158 catch {unset diffmergeid}
6159 if {![info exists treefilelist($id)]} {
6160 if {![info exists treepending]} {
6161 if {$id eq $nullid} {
6162 set cmd [list | git ls-files]
6163 } elseif {$id eq $nullid2} {
6164 set cmd [list | git ls-files --stage -t]
6166 set cmd [list | git ls-tree -r $id]
6168 if {[catch {set gtf [open $cmd r]}]} {
6172 set treefilelist($id) {}
6173 set treeidlist($id) {}
6174 fconfigure $gtf -blocking 0
6175 filerun $gtf [list gettreeline $gtf $id]
6182 proc gettreeline {gtf id} {
6183 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6186 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6187 if {$diffids eq $nullid} {
6190 set i [string first "\t" $line]
6191 if {$i < 0} continue
6192 set fname [string range $line [expr {$i+1}] end]
6193 set line [string range $line 0 [expr {$i-1}]]
6194 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6195 set sha1 [lindex $line 2]
6196 if {[string index $fname 0] eq "\""} {
6197 set fname [lindex $fname 0]
6199 lappend treeidlist($id) $sha1
6201 lappend treefilelist($id) $fname
6204 return [expr {$nl >= 1000? 2: 1}]
6208 if {$cmitmode ne "tree"} {
6209 if {![info exists diffmergeid]} {
6210 gettreediffs $diffids
6212 } elseif {$id ne $diffids} {
6221 global treefilelist treeidlist diffids nullid nullid2
6222 global ctext commentend
6224 set i [lsearch -exact $treefilelist($diffids) $f]
6226 puts "oops, $f not in list for id $diffids"
6229 if {$diffids eq $nullid} {
6230 if {[catch {set bf [open $f r]} err]} {
6231 puts "oops, can't read $f: $err"
6235 set blob [lindex $treeidlist($diffids) $i]
6236 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6237 puts "oops, error reading blob $blob: $err"
6241 fconfigure $bf -blocking 0
6242 filerun $bf [list getblobline $bf $diffids]
6243 $ctext config -state normal
6244 clear_ctext $commentend
6245 $ctext insert end "\n"
6246 $ctext insert end "$f\n" filesep
6247 $ctext config -state disabled
6248 $ctext yview $commentend
6252 proc getblobline {bf id} {
6253 global diffids cmitmode ctext
6255 if {$id ne $diffids || $cmitmode ne "tree"} {
6259 $ctext config -state normal
6261 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6262 $ctext insert end "$line\n"
6265 # delete last newline
6266 $ctext delete "end - 2c" "end - 1c"
6270 $ctext config -state disabled
6271 return [expr {$nl >= 1000? 2: 1}]
6274 proc mergediff {id} {
6275 global diffmergeid mdifffd
6279 global limitdiffs vfilelimit curview
6283 # this doesn't seem to actually affect anything...
6284 set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
6285 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6286 set cmd [concat $cmd -- $vfilelimit($curview)]
6288 if {[catch {set mdf [open $cmd r]} err]} {
6289 error_popup "[mc "Error getting merge diffs:"] $err"
6292 fconfigure $mdf -blocking 0
6293 set mdifffd($id) $mdf
6294 set np [llength $parents($curview,$id)]
6296 filerun $mdf [list getmergediffline $mdf $id $np]
6299 proc getmergediffline {mdf id np} {
6300 global diffmergeid ctext cflist mergemax
6301 global difffilestart mdifffd
6303 $ctext conf -state normal
6305 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
6306 if {![info exists diffmergeid] || $id != $diffmergeid
6307 || $mdf != $mdifffd($id)} {
6311 if {[regexp {^diff --cc (.*)} $line match fname]} {
6312 # start of a new file
6313 $ctext insert end "\n"
6314 set here [$ctext index "end - 1c"]
6315 lappend difffilestart $here
6316 add_flist [list $fname]
6317 set l [expr {(78 - [string length $fname]) / 2}]
6318 set pad [string range "----------------------------------------" 1 $l]
6319 $ctext insert end "$pad $fname $pad\n" filesep
6320 } elseif {[regexp {^@@} $line]} {
6321 $ctext insert end "$line\n" hunksep
6322 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
6325 # parse the prefix - one ' ', '-' or '+' for each parent
6330 for {set j 0} {$j < $np} {incr j} {
6331 set c [string range $line $j $j]
6334 } elseif {$c == "-"} {
6336 } elseif {$c == "+"} {
6345 if {!$isbad && $minuses ne {} && $pluses eq {}} {
6346 # line doesn't appear in result, parents in $minuses have the line
6347 set num [lindex $minuses 0]
6348 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
6349 # line appears in result, parents in $pluses don't have the line
6350 lappend tags mresult
6351 set num [lindex $spaces 0]
6354 if {$num >= $mergemax} {
6359 $ctext insert end "$line\n" $tags
6362 $ctext conf -state disabled
6367 return [expr {$nr >= 1000? 2: 1}]
6370 proc startdiff {ids} {
6371 global treediffs diffids treepending diffmergeid nullid nullid2
6375 catch {unset diffmergeid}
6376 if {![info exists treediffs($ids)] ||
6377 [lsearch -exact $ids $nullid] >= 0 ||
6378 [lsearch -exact $ids $nullid2] >= 0} {
6379 if {![info exists treepending]} {
6387 proc path_filter {filter name} {
6389 set l [string length $p]
6390 if {[string index $p end] eq "/"} {
6391 if {[string compare -length $l $p $name] == 0} {
6395 if {[string compare -length $l $p $name] == 0 &&
6396 ([string length $name] == $l ||
6397 [string index $name $l] eq "/")} {
6405 proc addtocflist {ids} {
6408 add_flist $treediffs($ids)
6412 proc diffcmd {ids flags} {
6413 global nullid nullid2
6415 set i [lsearch -exact $ids $nullid]
6416 set j [lsearch -exact $ids $nullid2]
6418 if {[llength $ids] > 1 && $j < 0} {
6419 # comparing working directory with some specific revision
6420 set cmd [concat | git diff-index $flags]
6422 lappend cmd -R [lindex $ids 1]
6424 lappend cmd [lindex $ids 0]
6427 # comparing working directory with index
6428 set cmd [concat | git diff-files $flags]
6433 } elseif {$j >= 0} {
6434 set cmd [concat | git diff-index --cached $flags]
6435 if {[llength $ids] > 1} {
6436 # comparing index with specific revision
6438 lappend cmd -R [lindex $ids 1]
6440 lappend cmd [lindex $ids 0]
6443 # comparing index with HEAD
6447 set cmd [concat | git diff-tree -r $flags $ids]
6452 proc gettreediffs {ids} {
6453 global treediff treepending
6455 set treepending $ids
6457 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6458 fconfigure $gdtf -blocking 0
6459 filerun $gdtf [list gettreediffline $gdtf $ids]
6462 proc gettreediffline {gdtf ids} {
6463 global treediff treediffs treepending diffids diffmergeid
6464 global cmitmode vfilelimit curview limitdiffs
6467 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
6468 set i [string first "\t" $line]
6470 set file [string range $line [expr {$i+1}] end]
6471 if {[string index $file 0] eq "\""} {
6472 set file [lindex $file 0]
6474 lappend treediff $file
6478 return [expr {$nr >= 1000? 2: 1}]
6481 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6483 foreach f $treediff {
6484 if {[path_filter $vfilelimit($curview) $f]} {
6488 set treediffs($ids) $flist
6490 set treediffs($ids) $treediff
6493 if {$cmitmode eq "tree"} {
6495 } elseif {$ids != $diffids} {
6496 if {![info exists diffmergeid]} {
6497 gettreediffs $diffids
6505 # empty string or positive integer
6506 proc diffcontextvalidate {v} {
6507 return [regexp {^(|[1-9][0-9]*)$} $v]
6510 proc diffcontextchange {n1 n2 op} {
6511 global diffcontextstring diffcontext
6513 if {[string is integer -strict $diffcontextstring]} {
6514 if {$diffcontextstring > 0} {
6515 set diffcontext $diffcontextstring
6521 proc changeignorespace {} {
6525 proc getblobdiffs {ids} {
6526 global blobdifffd diffids env
6527 global diffinhdr treediffs
6530 global limitdiffs vfilelimit curview
6532 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
6536 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6537 set cmd [concat $cmd -- $vfilelimit($curview)]
6539 if {[catch {set bdf [open $cmd r]} err]} {
6540 puts "error getting diffs: $err"
6544 fconfigure $bdf -blocking 0
6545 set blobdifffd($ids) $bdf
6546 filerun $bdf [list getblobdiffline $bdf $diffids]
6549 proc setinlist {var i val} {
6552 while {[llength [set $var]] < $i} {
6555 if {[llength [set $var]] == $i} {
6562 proc makediffhdr {fname ids} {
6563 global ctext curdiffstart treediffs
6565 set i [lsearch -exact $treediffs($ids) $fname]
6567 setinlist difffilestart $i $curdiffstart
6569 set l [expr {(78 - [string length $fname]) / 2}]
6570 set pad [string range "----------------------------------------" 1 $l]
6571 $ctext insert $curdiffstart "$pad $fname $pad" filesep
6574 proc getblobdiffline {bdf ids} {
6575 global diffids blobdifffd ctext curdiffstart
6576 global diffnexthead diffnextnote difffilestart
6577 global diffinhdr treediffs
6580 $ctext conf -state normal
6581 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
6582 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
6586 if {![string compare -length 11 "diff --git " $line]} {
6587 # trim off "diff --git "
6588 set line [string range $line 11 end]
6590 # start of a new file
6591 $ctext insert end "\n"
6592 set curdiffstart [$ctext index "end - 1c"]
6593 $ctext insert end "\n" filesep
6594 # If the name hasn't changed the length will be odd,
6595 # the middle char will be a space, and the two bits either
6596 # side will be a/name and b/name, or "a/name" and "b/name".
6597 # If the name has changed we'll get "rename from" and
6598 # "rename to" or "copy from" and "copy to" lines following this,
6599 # and we'll use them to get the filenames.
6600 # This complexity is necessary because spaces in the filename(s)
6601 # don't get escaped.
6602 set l [string length $line]
6603 set i [expr {$l / 2}]
6604 if {!(($l & 1) && [string index $line $i] eq " " &&
6605 [string range $line 2 [expr {$i - 1}]] eq \
6606 [string range $line [expr {$i + 3}] end])} {
6609 # unescape if quoted and chop off the a/ from the front
6610 if {[string index $line 0] eq "\""} {
6611 set fname [string range [lindex $line 0] 2 end]
6613 set fname [string range $line 2 [expr {$i - 1}]]
6615 makediffhdr $fname $ids
6617 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
6618 $line match f1l f1c f2l f2c rest]} {
6619 $ctext insert end "$line\n" hunksep
6622 } elseif {$diffinhdr} {
6623 if {![string compare -length 12 "rename from " $line]} {
6624 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
6625 if {[string index $fname 0] eq "\""} {
6626 set fname [lindex $fname 0]
6628 set i [lsearch -exact $treediffs($ids) $fname]
6630 setinlist difffilestart $i $curdiffstart
6632 } elseif {![string compare -length 10 $line "rename to "] ||
6633 ![string compare -length 8 $line "copy to "]} {
6634 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
6635 if {[string index $fname 0] eq "\""} {
6636 set fname [lindex $fname 0]
6638 makediffhdr $fname $ids
6639 } elseif {[string compare -length 3 $line "---"] == 0} {
6642 } elseif {[string compare -length 3 $line "+++"] == 0} {
6646 $ctext insert end "$line\n" filesep
6649 set x [string range $line 0 0]
6650 if {$x == "-" || $x == "+"} {
6651 set tag [expr {$x == "+"}]
6652 $ctext insert end "$line\n" d$tag
6653 } elseif {$x == " "} {
6654 $ctext insert end "$line\n"
6656 # "\ No newline at end of file",
6657 # or something else we don't recognize
6658 $ctext insert end "$line\n" hunksep
6662 $ctext conf -state disabled
6667 return [expr {$nr >= 1000? 2: 1}]
6670 proc changediffdisp {} {
6671 global ctext diffelide
6673 $ctext tag conf d0 -elide [lindex $diffelide 0]
6674 $ctext tag conf d1 -elide [lindex $diffelide 1]
6677 proc highlightfile {loc cline} {
6678 global ctext cflist cflist_top
6681 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
6682 $cflist tag add highlight $cline.0 "$cline.0 lineend"
6683 $cflist see $cline.0
6684 set cflist_top $cline
6688 global difffilestart ctext cmitmode
6690 if {$cmitmode eq "tree"} return
6693 set here [$ctext index @0,0]
6694 foreach loc $difffilestart {
6695 if {[$ctext compare $loc >= $here]} {
6696 highlightfile $prev $prevline
6702 highlightfile $prev $prevline
6706 global difffilestart ctext cmitmode
6708 if {$cmitmode eq "tree"} return
6709 set here [$ctext index @0,0]
6711 foreach loc $difffilestart {
6713 if {[$ctext compare $loc > $here]} {
6714 highlightfile $loc $line
6720 proc clear_ctext {{first 1.0}} {
6721 global ctext smarktop smarkbot
6724 set l [lindex [split $first .] 0]
6725 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
6728 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
6731 $ctext delete $first end
6732 if {$first eq "1.0"} {
6733 catch {unset pendinglinks}
6737 proc settabs {{firstab {}}} {
6738 global firsttabstop tabstop ctext have_tk85
6740 if {$firstab ne {} && $have_tk85} {
6741 set firsttabstop $firstab
6743 set w [font measure textfont "0"]
6744 if {$firsttabstop != 0} {
6745 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
6746 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
6747 } elseif {$have_tk85 || $tabstop != 8} {
6748 $ctext conf -tabs [expr {$tabstop * $w}]
6750 $ctext conf -tabs {}
6754 proc incrsearch {name ix op} {
6755 global ctext searchstring searchdirn
6757 $ctext tag remove found 1.0 end
6758 if {[catch {$ctext index anchor}]} {
6759 # no anchor set, use start of selection, or of visible area
6760 set sel [$ctext tag ranges sel]
6762 $ctext mark set anchor [lindex $sel 0]
6763 } elseif {$searchdirn eq "-forwards"} {
6764 $ctext mark set anchor @0,0
6766 $ctext mark set anchor @0,[winfo height $ctext]
6769 if {$searchstring ne {}} {
6770 set here [$ctext search $searchdirn -- $searchstring anchor]
6779 global sstring ctext searchstring searchdirn
6782 $sstring icursor end
6783 set searchdirn -forwards
6784 if {$searchstring ne {}} {
6785 set sel [$ctext tag ranges sel]
6787 set start "[lindex $sel 0] + 1c"
6788 } elseif {[catch {set start [$ctext index anchor]}]} {
6791 set match [$ctext search -count mlen -- $searchstring $start]
6792 $ctext tag remove sel 1.0 end
6798 set mend "$match + $mlen c"
6799 $ctext tag add sel $match $mend
6800 $ctext mark unset anchor
6804 proc dosearchback {} {
6805 global sstring ctext searchstring searchdirn
6808 $sstring icursor end
6809 set searchdirn -backwards
6810 if {$searchstring ne {}} {
6811 set sel [$ctext tag ranges sel]
6813 set start [lindex $sel 0]
6814 } elseif {[catch {set start [$ctext index anchor]}]} {
6815 set start @0,[winfo height $ctext]
6817 set match [$ctext search -backwards -count ml -- $searchstring $start]
6818 $ctext tag remove sel 1.0 end
6824 set mend "$match + $ml c"
6825 $ctext tag add sel $match $mend
6826 $ctext mark unset anchor
6830 proc searchmark {first last} {
6831 global ctext searchstring
6835 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
6836 if {$match eq {}} break
6837 set mend "$match + $mlen c"
6838 $ctext tag add found $match $mend
6842 proc searchmarkvisible {doall} {
6843 global ctext smarktop smarkbot
6845 set topline [lindex [split [$ctext index @0,0] .] 0]
6846 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
6847 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
6848 # no overlap with previous
6849 searchmark $topline $botline
6850 set smarktop $topline
6851 set smarkbot $botline
6853 if {$topline < $smarktop} {
6854 searchmark $topline [expr {$smarktop-1}]
6855 set smarktop $topline
6857 if {$botline > $smarkbot} {
6858 searchmark [expr {$smarkbot+1}] $botline
6859 set smarkbot $botline
6864 proc scrolltext {f0 f1} {
6867 .bleft.bottom.sb set $f0 $f1
6868 if {$searchstring ne {}} {
6874 global linespc charspc canvx0 canvy0
6875 global xspc1 xspc2 lthickness
6877 set linespc [font metrics mainfont -linespace]
6878 set charspc [font measure mainfont "m"]
6879 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
6880 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
6881 set lthickness [expr {int($linespc / 9) + 1}]
6882 set xspc1(0) $linespc
6890 set ymax [lindex [$canv cget -scrollregion] 3]
6891 if {$ymax eq {} || $ymax == 0} return
6892 set span [$canv yview]
6895 allcanvs yview moveto [lindex $span 0]
6897 if {$selectedline ne {}} {
6898 selectline $selectedline 0
6899 allcanvs yview moveto [lindex $span 0]
6903 proc parsefont {f n} {
6906 set fontattr($f,family) [lindex $n 0]
6908 if {$s eq {} || $s == 0} {
6911 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
6913 set fontattr($f,size) $s
6914 set fontattr($f,weight) normal
6915 set fontattr($f,slant) roman
6916 foreach style [lrange $n 2 end] {
6919 "bold" {set fontattr($f,weight) $style}
6921 "italic" {set fontattr($f,slant) $style}
6926 proc fontflags {f {isbold 0}} {
6929 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
6930 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
6931 -slant $fontattr($f,slant)]
6937 set n [list $fontattr($f,family) $fontattr($f,size)]
6938 if {$fontattr($f,weight) eq "bold"} {
6941 if {$fontattr($f,slant) eq "italic"} {
6947 proc incrfont {inc} {
6948 global mainfont textfont ctext canv cflist showrefstop
6949 global stopped entries fontattr
6952 set s $fontattr(mainfont,size)
6957 set fontattr(mainfont,size) $s
6958 font config mainfont -size $s
6959 font config mainfontbold -size $s
6960 set mainfont [fontname mainfont]
6961 set s $fontattr(textfont,size)
6966 set fontattr(textfont,size) $s
6967 font config textfont -size $s
6968 font config textfontbold -size $s
6969 set textfont [fontname textfont]
6976 global sha1entry sha1string
6977 if {[string length $sha1string] == 40} {
6978 $sha1entry delete 0 end
6982 proc sha1change {n1 n2 op} {
6983 global sha1string currentid sha1but
6984 if {$sha1string == {}
6985 || ([info exists currentid] && $sha1string == $currentid)} {
6990 if {[$sha1but cget -state] == $state} return
6991 if {$state == "normal"} {
6992 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
6994 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
6998 proc gotocommit {} {
6999 global sha1string tagids headids curview varcid
7001 if {$sha1string == {}
7002 || ([info exists currentid] && $sha1string == $currentid)} return
7003 if {[info exists tagids($sha1string)]} {
7004 set id $tagids($sha1string)
7005 } elseif {[info exists headids($sha1string)]} {
7006 set id $headids($sha1string)
7008 set id [string tolower $sha1string]
7009 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7010 set matches [array names varcid "$curview,$id*"]
7011 if {$matches ne {}} {
7012 if {[llength $matches] > 1} {
7013 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7016 set id [lindex [split [lindex $matches 0] ","] 1]
7020 if {[commitinview $id $curview]} {
7021 selectline [rowofcommit $id] 1
7024 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7025 set msg [mc "SHA1 id %s is not known" $sha1string]
7027 set msg [mc "Tag/Head %s is not known" $sha1string]
7032 proc lineenter {x y id} {
7033 global hoverx hovery hoverid hovertimer
7034 global commitinfo canv
7036 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7040 if {[info exists hovertimer]} {
7041 after cancel $hovertimer
7043 set hovertimer [after 500 linehover]
7047 proc linemotion {x y id} {
7048 global hoverx hovery hoverid hovertimer
7050 if {[info exists hoverid] && $id == $hoverid} {
7053 if {[info exists hovertimer]} {
7054 after cancel $hovertimer
7056 set hovertimer [after 500 linehover]
7060 proc lineleave {id} {
7061 global hoverid hovertimer canv
7063 if {[info exists hoverid] && $id == $hoverid} {
7065 if {[info exists hovertimer]} {
7066 after cancel $hovertimer
7074 global hoverx hovery hoverid hovertimer
7075 global canv linespc lthickness
7078 set text [lindex $commitinfo($hoverid) 0]
7079 set ymax [lindex [$canv cget -scrollregion] 3]
7080 if {$ymax == {}} return
7081 set yfrac [lindex [$canv yview] 0]
7082 set x [expr {$hoverx + 2 * $linespc}]
7083 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7084 set x0 [expr {$x - 2 * $lthickness}]
7085 set y0 [expr {$y - 2 * $lthickness}]
7086 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7087 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7088 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7089 -fill \#ffff80 -outline black -width 1 -tags hover]
7091 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7096 proc clickisonarrow {id y} {
7099 set ranges [rowranges $id]
7100 set thresh [expr {2 * $lthickness + 6}]
7101 set n [expr {[llength $ranges] - 1}]
7102 for {set i 1} {$i < $n} {incr i} {
7103 set row [lindex $ranges $i]
7104 if {abs([yc $row] - $y) < $thresh} {
7111 proc arrowjump {id n y} {
7114 # 1 <-> 2, 3 <-> 4, etc...
7115 set n [expr {(($n - 1) ^ 1) + 1}]
7116 set row [lindex [rowranges $id] $n]
7118 set ymax [lindex [$canv cget -scrollregion] 3]
7119 if {$ymax eq {} || $ymax <= 0} return
7120 set view [$canv yview]
7121 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7122 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7126 allcanvs yview moveto $yfrac
7129 proc lineclick {x y id isnew} {
7130 global ctext commitinfo children canv thickerline curview
7132 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7137 # draw this line thicker than normal
7141 set ymax [lindex [$canv cget -scrollregion] 3]
7142 if {$ymax eq {}} return
7143 set yfrac [lindex [$canv yview] 0]
7144 set y [expr {$y + $yfrac * $ymax}]
7146 set dirn [clickisonarrow $id $y]
7148 arrowjump $id $dirn $y
7153 addtohistory [list lineclick $x $y $id 0]
7155 # fill the details pane with info about this line
7156 $ctext conf -state normal
7159 $ctext insert end "[mc "Parent"]:\t"
7160 $ctext insert end $id link0
7162 set info $commitinfo($id)
7163 $ctext insert end "\n\t[lindex $info 0]\n"
7164 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7165 set date [formatdate [lindex $info 2]]
7166 $ctext insert end "\t[mc "Date"]:\t$date\n"
7167 set kids $children($curview,$id)
7169 $ctext insert end "\n[mc "Children"]:"
7171 foreach child $kids {
7173 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7174 set info $commitinfo($child)
7175 $ctext insert end "\n\t"
7176 $ctext insert end $child link$i
7177 setlink $child link$i
7178 $ctext insert end "\n\t[lindex $info 0]"
7179 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7180 set date [formatdate [lindex $info 2]]
7181 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7184 $ctext conf -state disabled
7188 proc normalline {} {
7190 if {[info exists thickerline]} {
7199 if {[commitinview $id $curview]} {
7200 selectline [rowofcommit $id] 1
7206 if {![info exists startmstime]} {
7207 set startmstime [clock clicks -milliseconds]
7209 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7212 proc rowmenu {x y id} {
7213 global rowctxmenu selectedline rowmenuid curview
7214 global nullid nullid2 fakerowmenu mainhead
7218 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7223 if {$id ne $nullid && $id ne $nullid2} {
7224 set menu $rowctxmenu
7225 if {$mainhead ne {}} {
7226 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7228 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7231 set menu $fakerowmenu
7233 $menu entryconfigure [mc "Diff this -> selected"] -state $state
7234 $menu entryconfigure [mc "Diff selected -> this"] -state $state
7235 $menu entryconfigure [mc "Make patch"] -state $state
7236 tk_popup $menu $x $y
7239 proc diffvssel {dirn} {
7240 global rowmenuid selectedline
7242 if {$selectedline eq {}} return
7244 set oldid [commitonrow $selectedline]
7245 set newid $rowmenuid
7247 set oldid $rowmenuid
7248 set newid [commitonrow $selectedline]
7250 addtohistory [list doseldiff $oldid $newid]
7251 doseldiff $oldid $newid
7254 proc doseldiff {oldid newid} {
7258 $ctext conf -state normal
7260 init_flist [mc "Top"]
7261 $ctext insert end "[mc "From"] "
7262 $ctext insert end $oldid link0
7263 setlink $oldid link0
7264 $ctext insert end "\n "
7265 $ctext insert end [lindex $commitinfo($oldid) 0]
7266 $ctext insert end "\n\n[mc "To"] "
7267 $ctext insert end $newid link1
7268 setlink $newid link1
7269 $ctext insert end "\n "
7270 $ctext insert end [lindex $commitinfo($newid) 0]
7271 $ctext insert end "\n"
7272 $ctext conf -state disabled
7273 $ctext tag remove found 1.0 end
7274 startdiff [list $oldid $newid]
7278 global rowmenuid currentid commitinfo patchtop patchnum
7280 if {![info exists currentid]} return
7281 set oldid $currentid
7282 set oldhead [lindex $commitinfo($oldid) 0]
7283 set newid $rowmenuid
7284 set newhead [lindex $commitinfo($newid) 0]
7287 catch {destroy $top}
7289 label $top.title -text [mc "Generate patch"]
7290 grid $top.title - -pady 10
7291 label $top.from -text [mc "From:"]
7292 entry $top.fromsha1 -width 40 -relief flat
7293 $top.fromsha1 insert 0 $oldid
7294 $top.fromsha1 conf -state readonly
7295 grid $top.from $top.fromsha1 -sticky w
7296 entry $top.fromhead -width 60 -relief flat
7297 $top.fromhead insert 0 $oldhead
7298 $top.fromhead conf -state readonly
7299 grid x $top.fromhead -sticky w
7300 label $top.to -text [mc "To:"]
7301 entry $top.tosha1 -width 40 -relief flat
7302 $top.tosha1 insert 0 $newid
7303 $top.tosha1 conf -state readonly
7304 grid $top.to $top.tosha1 -sticky w
7305 entry $top.tohead -width 60 -relief flat
7306 $top.tohead insert 0 $newhead
7307 $top.tohead conf -state readonly
7308 grid x $top.tohead -sticky w
7309 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7310 grid $top.rev x -pady 10
7311 label $top.flab -text [mc "Output file:"]
7312 entry $top.fname -width 60
7313 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7315 grid $top.flab $top.fname -sticky w
7317 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7318 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7319 grid $top.buts.gen $top.buts.can
7320 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7321 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7322 grid $top.buts - -pady 10 -sticky ew
7326 proc mkpatchrev {} {
7329 set oldid [$patchtop.fromsha1 get]
7330 set oldhead [$patchtop.fromhead get]
7331 set newid [$patchtop.tosha1 get]
7332 set newhead [$patchtop.tohead get]
7333 foreach e [list fromsha1 fromhead tosha1 tohead] \
7334 v [list $newid $newhead $oldid $oldhead] {
7335 $patchtop.$e conf -state normal
7336 $patchtop.$e delete 0 end
7337 $patchtop.$e insert 0 $v
7338 $patchtop.$e conf -state readonly
7343 global patchtop nullid nullid2
7345 set oldid [$patchtop.fromsha1 get]
7346 set newid [$patchtop.tosha1 get]
7347 set fname [$patchtop.fname get]
7348 set cmd [diffcmd [list $oldid $newid] -p]
7349 # trim off the initial "|"
7350 set cmd [lrange $cmd 1 end]
7351 lappend cmd >$fname &
7352 if {[catch {eval exec $cmd} err]} {
7353 error_popup "[mc "Error creating patch:"] $err"
7355 catch {destroy $patchtop}
7359 proc mkpatchcan {} {
7362 catch {destroy $patchtop}
7367 global rowmenuid mktagtop commitinfo
7371 catch {destroy $top}
7373 label $top.title -text [mc "Create tag"]
7374 grid $top.title - -pady 10
7375 label $top.id -text [mc "ID:"]
7376 entry $top.sha1 -width 40 -relief flat
7377 $top.sha1 insert 0 $rowmenuid
7378 $top.sha1 conf -state readonly
7379 grid $top.id $top.sha1 -sticky w
7380 entry $top.head -width 60 -relief flat
7381 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7382 $top.head conf -state readonly
7383 grid x $top.head -sticky w
7384 label $top.tlab -text [mc "Tag name:"]
7385 entry $top.tag -width 60
7386 grid $top.tlab $top.tag -sticky w
7388 button $top.buts.gen -text [mc "Create"] -command mktaggo
7389 button $top.buts.can -text [mc "Cancel"] -command mktagcan
7390 grid $top.buts.gen $top.buts.can
7391 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7392 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7393 grid $top.buts - -pady 10 -sticky ew
7398 global mktagtop env tagids idtags
7400 set id [$mktagtop.sha1 get]
7401 set tag [$mktagtop.tag get]
7403 error_popup [mc "No tag name specified"]
7406 if {[info exists tagids($tag)]} {
7407 error_popup [mc "Tag \"%s\" already exists" $tag]
7411 exec git tag $tag $id
7413 error_popup "[mc "Error creating tag:"] $err"
7417 set tagids($tag) $id
7418 lappend idtags($id) $tag
7425 proc redrawtags {id} {
7426 global canv linehtag idpos currentid curview cmitlisted
7427 global canvxmax iddrawn circleitem mainheadid circlecolors
7429 if {![commitinview $id $curview]} return
7430 if {![info exists iddrawn($id)]} return
7431 set row [rowofcommit $id]
7432 if {$id eq $mainheadid} {
7435 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
7437 $canv itemconf $circleitem($row) -fill $ofill
7438 $canv delete tag.$id
7439 set xt [eval drawtags $id $idpos($id)]
7440 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
7441 set text [$canv itemcget $linehtag($row) -text]
7442 set font [$canv itemcget $linehtag($row) -font]
7443 set xr [expr {$xt + [font measure $font $text]}]
7444 if {$xr > $canvxmax} {
7448 if {[info exists currentid] && $currentid == $id} {
7456 catch {destroy $mktagtop}
7465 proc writecommit {} {
7466 global rowmenuid wrcomtop commitinfo wrcomcmd
7468 set top .writecommit
7470 catch {destroy $top}
7472 label $top.title -text [mc "Write commit to file"]
7473 grid $top.title - -pady 10
7474 label $top.id -text [mc "ID:"]
7475 entry $top.sha1 -width 40 -relief flat
7476 $top.sha1 insert 0 $rowmenuid
7477 $top.sha1 conf -state readonly
7478 grid $top.id $top.sha1 -sticky w
7479 entry $top.head -width 60 -relief flat
7480 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7481 $top.head conf -state readonly
7482 grid x $top.head -sticky w
7483 label $top.clab -text [mc "Command:"]
7484 entry $top.cmd -width 60 -textvariable wrcomcmd
7485 grid $top.clab $top.cmd -sticky w -pady 10
7486 label $top.flab -text [mc "Output file:"]
7487 entry $top.fname -width 60
7488 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
7489 grid $top.flab $top.fname -sticky w
7491 button $top.buts.gen -text [mc "Write"] -command wrcomgo
7492 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
7493 grid $top.buts.gen $top.buts.can
7494 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7495 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7496 grid $top.buts - -pady 10 -sticky ew
7503 set id [$wrcomtop.sha1 get]
7504 set cmd "echo $id | [$wrcomtop.cmd get]"
7505 set fname [$wrcomtop.fname get]
7506 if {[catch {exec sh -c $cmd >$fname &} err]} {
7507 error_popup "[mc "Error writing commit:"] $err"
7509 catch {destroy $wrcomtop}
7516 catch {destroy $wrcomtop}
7521 global rowmenuid mkbrtop
7524 catch {destroy $top}
7526 label $top.title -text [mc "Create new branch"]
7527 grid $top.title - -pady 10
7528 label $top.id -text [mc "ID:"]
7529 entry $top.sha1 -width 40 -relief flat
7530 $top.sha1 insert 0 $rowmenuid
7531 $top.sha1 conf -state readonly
7532 grid $top.id $top.sha1 -sticky w
7533 label $top.nlab -text [mc "Name:"]
7534 entry $top.name -width 40
7535 grid $top.nlab $top.name -sticky w
7537 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
7538 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
7539 grid $top.buts.go $top.buts.can
7540 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7541 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7542 grid $top.buts - -pady 10 -sticky ew
7547 global headids idheads
7549 set name [$top.name get]
7550 set id [$top.sha1 get]
7552 error_popup [mc "Please specify a name for the new branch"]
7555 catch {destroy $top}
7559 exec git branch $name $id
7564 set headids($name) $id
7565 lappend idheads($id) $name
7574 proc cherrypick {} {
7575 global rowmenuid curview
7576 global mainhead mainheadid
7578 set oldhead [exec git rev-parse HEAD]
7579 set dheads [descheads $rowmenuid]
7580 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
7581 set ok [confirm_popup [mc "Commit %s is already\
7582 included in branch %s -- really re-apply it?" \
7583 [string range $rowmenuid 0 7] $mainhead]]
7586 nowbusy cherrypick [mc "Cherry-picking"]
7588 # Unfortunately git-cherry-pick writes stuff to stderr even when
7589 # no error occurs, and exec takes that as an indication of error...
7590 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
7595 set newhead [exec git rev-parse HEAD]
7596 if {$newhead eq $oldhead} {
7598 error_popup [mc "No changes committed"]
7601 addnewchild $newhead $oldhead
7602 if {[commitinview $oldhead $curview]} {
7603 insertrow $newhead $oldhead $curview
7604 if {$mainhead ne {}} {
7605 movehead $newhead $mainhead
7606 movedhead $newhead $mainhead
7608 set mainheadid $newhead
7617 global mainhead rowmenuid confirm_ok resettype
7620 set w ".confirmreset"
7623 wm title $w [mc "Confirm reset"]
7624 message $w.m -text \
7625 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
7626 -justify center -aspect 1000
7627 pack $w.m -side top -fill x -padx 20 -pady 20
7628 frame $w.f -relief sunken -border 2
7629 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
7630 grid $w.f.rt -sticky w
7632 radiobutton $w.f.soft -value soft -variable resettype -justify left \
7633 -text [mc "Soft: Leave working tree and index untouched"]
7634 grid $w.f.soft -sticky w
7635 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
7636 -text [mc "Mixed: Leave working tree untouched, reset index"]
7637 grid $w.f.mixed -sticky w
7638 radiobutton $w.f.hard -value hard -variable resettype -justify left \
7639 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
7640 grid $w.f.hard -sticky w
7641 pack $w.f -side top -fill x
7642 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
7643 pack $w.ok -side left -fill x -padx 20 -pady 20
7644 button $w.cancel -text [mc Cancel] -command "destroy $w"
7645 pack $w.cancel -side right -fill x -padx 20 -pady 20
7646 bind $w <Visibility> "grab $w; focus $w"
7648 if {!$confirm_ok} return
7649 if {[catch {set fd [open \
7650 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
7654 filerun $fd [list readresetstat $fd]
7655 nowbusy reset [mc "Resetting"]
7660 proc readresetstat {fd} {
7661 global mainhead mainheadid showlocalchanges rprogcoord
7663 if {[gets $fd line] >= 0} {
7664 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7665 set rprogcoord [expr {1.0 * $m / $n}]
7673 if {[catch {close $fd} err]} {
7676 set oldhead $mainheadid
7677 set newhead [exec git rev-parse HEAD]
7678 if {$newhead ne $oldhead} {
7679 movehead $newhead $mainhead
7680 movedhead $newhead $mainhead
7681 set mainheadid $newhead
7685 if {$showlocalchanges} {
7691 # context menu for a head
7692 proc headmenu {x y id head} {
7693 global headmenuid headmenuhead headctxmenu mainhead
7697 set headmenuhead $head
7699 if {$head eq $mainhead} {
7702 $headctxmenu entryconfigure 0 -state $state
7703 $headctxmenu entryconfigure 1 -state $state
7704 tk_popup $headctxmenu $x $y
7708 global headmenuid headmenuhead headids
7709 global showlocalchanges mainheadid
7711 # check the tree is clean first??
7712 nowbusy checkout [mc "Checking out"]
7716 set fd [open [list | git checkout $headmenuhead 2>@1] r]
7720 if {$showlocalchanges} {
7724 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
7728 proc readcheckoutstat {fd newhead newheadid} {
7729 global mainhead mainheadid headids showlocalchanges progresscoords
7731 if {[gets $fd line] >= 0} {
7732 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7733 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
7738 set progresscoords {0 0}
7741 if {[catch {close $fd} err]} {
7744 set oldmainid $mainheadid
7745 set mainhead $newhead
7746 set mainheadid $newheadid
7747 redrawtags $oldmainid
7748 redrawtags $newheadid
7750 if {$showlocalchanges} {
7756 global headmenuid headmenuhead mainhead
7759 set head $headmenuhead
7761 # this check shouldn't be needed any more...
7762 if {$head eq $mainhead} {
7763 error_popup [mc "Cannot delete the currently checked-out branch"]
7766 set dheads [descheads $id]
7767 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
7768 # the stuff on this branch isn't on any other branch
7769 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
7770 branch.\nReally delete branch %s?" $head $head]]} return
7774 if {[catch {exec git branch -D $head} err]} {
7779 removehead $id $head
7780 removedhead $id $head
7787 # Display a list of tags and heads
7789 global showrefstop bgcolor fgcolor selectbgcolor
7790 global bglist fglist reflistfilter reflist maincursor
7793 set showrefstop $top
7794 if {[winfo exists $top]} {
7800 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
7801 text $top.list -background $bgcolor -foreground $fgcolor \
7802 -selectbackground $selectbgcolor -font mainfont \
7803 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
7804 -width 30 -height 20 -cursor $maincursor \
7805 -spacing1 1 -spacing3 1 -state disabled
7806 $top.list tag configure highlight -background $selectbgcolor
7807 lappend bglist $top.list
7808 lappend fglist $top.list
7809 scrollbar $top.ysb -command "$top.list yview" -orient vertical
7810 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
7811 grid $top.list $top.ysb -sticky nsew
7812 grid $top.xsb x -sticky ew
7814 label $top.f.l -text "[mc "Filter"]: "
7815 entry $top.f.e -width 20 -textvariable reflistfilter
7816 set reflistfilter "*"
7817 trace add variable reflistfilter write reflistfilter_change
7818 pack $top.f.e -side right -fill x -expand 1
7819 pack $top.f.l -side left
7820 grid $top.f - -sticky ew -pady 2
7821 button $top.close -command [list destroy $top] -text [mc "Close"]
7823 grid columnconfigure $top 0 -weight 1
7824 grid rowconfigure $top 0 -weight 1
7825 bind $top.list <1> {break}
7826 bind $top.list <B1-Motion> {break}
7827 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
7832 proc sel_reflist {w x y} {
7833 global showrefstop reflist headids tagids otherrefids
7835 if {![winfo exists $showrefstop]} return
7836 set l [lindex [split [$w index "@$x,$y"] "."] 0]
7837 set ref [lindex $reflist [expr {$l-1}]]
7838 set n [lindex $ref 0]
7839 switch -- [lindex $ref 1] {
7840 "H" {selbyid $headids($n)}
7841 "T" {selbyid $tagids($n)}
7842 "o" {selbyid $otherrefids($n)}
7844 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
7847 proc unsel_reflist {} {
7850 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7851 $showrefstop.list tag remove highlight 0.0 end
7854 proc reflistfilter_change {n1 n2 op} {
7855 global reflistfilter
7857 after cancel refill_reflist
7858 after 200 refill_reflist
7861 proc refill_reflist {} {
7862 global reflist reflistfilter showrefstop headids tagids otherrefids
7863 global curview commitinterest
7865 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7867 foreach n [array names headids] {
7868 if {[string match $reflistfilter $n]} {
7869 if {[commitinview $headids($n) $curview]} {
7870 lappend refs [list $n H]
7872 set commitinterest($headids($n)) {run refill_reflist}
7876 foreach n [array names tagids] {
7877 if {[string match $reflistfilter $n]} {
7878 if {[commitinview $tagids($n) $curview]} {
7879 lappend refs [list $n T]
7881 set commitinterest($tagids($n)) {run refill_reflist}
7885 foreach n [array names otherrefids] {
7886 if {[string match $reflistfilter $n]} {
7887 if {[commitinview $otherrefids($n) $curview]} {
7888 lappend refs [list $n o]
7890 set commitinterest($otherrefids($n)) {run refill_reflist}
7894 set refs [lsort -index 0 $refs]
7895 if {$refs eq $reflist} return
7897 # Update the contents of $showrefstop.list according to the
7898 # differences between $reflist (old) and $refs (new)
7899 $showrefstop.list conf -state normal
7900 $showrefstop.list insert end "\n"
7903 while {$i < [llength $reflist] || $j < [llength $refs]} {
7904 if {$i < [llength $reflist]} {
7905 if {$j < [llength $refs]} {
7906 set cmp [string compare [lindex $reflist $i 0] \
7907 [lindex $refs $j 0]]
7909 set cmp [string compare [lindex $reflist $i 1] \
7910 [lindex $refs $j 1]]
7920 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
7928 set l [expr {$j + 1}]
7929 $showrefstop.list image create $l.0 -align baseline \
7930 -image reficon-[lindex $refs $j 1] -padx 2
7931 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
7937 # delete last newline
7938 $showrefstop.list delete end-2c end-1c
7939 $showrefstop.list conf -state disabled
7942 # Stuff for finding nearby tags
7943 proc getallcommits {} {
7944 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
7945 global idheads idtags idotherrefs allparents tagobjid
7947 if {![info exists allcommits]} {
7953 set allccache [file join [gitdir] "gitk.cache"]
7955 set f [open $allccache r]
7964 set cmd [list | git rev-list --parents]
7965 set allcupdate [expr {$seeds ne {}}]
7969 set refs [concat [array names idheads] [array names idtags] \
7970 [array names idotherrefs]]
7973 foreach name [array names tagobjid] {
7974 lappend tagobjs $tagobjid($name)
7976 foreach id [lsort -unique $refs] {
7977 if {![info exists allparents($id)] &&
7978 [lsearch -exact $tagobjs $id] < 0} {
7989 set fd [open [concat $cmd $ids] r]
7990 fconfigure $fd -blocking 0
7993 filerun $fd [list getallclines $fd]
7999 # Since most commits have 1 parent and 1 child, we group strings of
8000 # such commits into "arcs" joining branch/merge points (BMPs), which
8001 # are commits that either don't have 1 parent or don't have 1 child.
8003 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8004 # arcout(id) - outgoing arcs for BMP
8005 # arcids(a) - list of IDs on arc including end but not start
8006 # arcstart(a) - BMP ID at start of arc
8007 # arcend(a) - BMP ID at end of arc
8008 # growing(a) - arc a is still growing
8009 # arctags(a) - IDs out of arcids (excluding end) that have tags
8010 # archeads(a) - IDs out of arcids (excluding end) that have heads
8011 # The start of an arc is at the descendent end, so "incoming" means
8012 # coming from descendents, and "outgoing" means going towards ancestors.
8014 proc getallclines {fd} {
8015 global allparents allchildren idtags idheads nextarc
8016 global arcnos arcids arctags arcout arcend arcstart archeads growing
8017 global seeds allcommits cachedarcs allcupdate
8020 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8021 set id [lindex $line 0]
8022 if {[info exists allparents($id)]} {
8027 set olds [lrange $line 1 end]
8028 set allparents($id) $olds
8029 if {![info exists allchildren($id)]} {
8030 set allchildren($id) {}
8035 if {[llength $olds] == 1 && [llength $a] == 1} {
8036 lappend arcids($a) $id
8037 if {[info exists idtags($id)]} {
8038 lappend arctags($a) $id
8040 if {[info exists idheads($id)]} {
8041 lappend archeads($a) $id
8043 if {[info exists allparents($olds)]} {
8044 # seen parent already
8045 if {![info exists arcout($olds)]} {
8048 lappend arcids($a) $olds
8049 set arcend($a) $olds
8052 lappend allchildren($olds) $id
8053 lappend arcnos($olds) $a
8057 foreach a $arcnos($id) {
8058 lappend arcids($a) $id
8065 lappend allchildren($p) $id
8066 set a [incr nextarc]
8067 set arcstart($a) $id
8074 if {[info exists allparents($p)]} {
8075 # seen it already, may need to make a new branch
8076 if {![info exists arcout($p)]} {
8079 lappend arcids($a) $p
8083 lappend arcnos($p) $a
8088 global cached_dheads cached_dtags cached_atags
8089 catch {unset cached_dheads}
8090 catch {unset cached_dtags}
8091 catch {unset cached_atags}
8094 return [expr {$nid >= 1000? 2: 1}]
8098 fconfigure $fd -blocking 1
8101 # got an error reading the list of commits
8102 # if we were updating, try rereading the whole thing again
8108 error_popup "[mc "Error reading commit topology information;\
8109 branch and preceding/following tag information\
8110 will be incomplete."]\n($err)"
8113 if {[incr allcommits -1] == 0} {
8123 proc recalcarc {a} {
8124 global arctags archeads arcids idtags idheads
8128 foreach id [lrange $arcids($a) 0 end-1] {
8129 if {[info exists idtags($id)]} {
8132 if {[info exists idheads($id)]} {
8137 set archeads($a) $ah
8141 global arcnos arcids nextarc arctags archeads idtags idheads
8142 global arcstart arcend arcout allparents growing
8145 if {[llength $a] != 1} {
8146 puts "oops splitarc called but [llength $a] arcs already"
8150 set i [lsearch -exact $arcids($a) $p]
8152 puts "oops splitarc $p not in arc $a"
8155 set na [incr nextarc]
8156 if {[info exists arcend($a)]} {
8157 set arcend($na) $arcend($a)
8159 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8160 set j [lsearch -exact $arcnos($l) $a]
8161 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8163 set tail [lrange $arcids($a) [expr {$i+1}] end]
8164 set arcids($a) [lrange $arcids($a) 0 $i]
8166 set arcstart($na) $p
8168 set arcids($na) $tail
8169 if {[info exists growing($a)]} {
8175 if {[llength $arcnos($id)] == 1} {
8178 set j [lsearch -exact $arcnos($id) $a]
8179 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8183 # reconstruct tags and heads lists
8184 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8189 set archeads($na) {}
8193 # Update things for a new commit added that is a child of one
8194 # existing commit. Used when cherry-picking.
8195 proc addnewchild {id p} {
8196 global allparents allchildren idtags nextarc
8197 global arcnos arcids arctags arcout arcend arcstart archeads growing
8198 global seeds allcommits
8200 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8201 set allparents($id) [list $p]
8202 set allchildren($id) {}
8205 lappend allchildren($p) $id
8206 set a [incr nextarc]
8207 set arcstart($a) $id
8210 set arcids($a) [list $p]
8212 if {![info exists arcout($p)]} {
8215 lappend arcnos($p) $a
8216 set arcout($id) [list $a]
8219 # This implements a cache for the topology information.
8220 # The cache saves, for each arc, the start and end of the arc,
8221 # the ids on the arc, and the outgoing arcs from the end.
8222 proc readcache {f} {
8223 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8224 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8229 if {$lim - $a > 500} {
8230 set lim [expr {$a + 500}]
8234 # finish reading the cache and setting up arctags, etc.
8236 if {$line ne "1"} {error "bad final version"}
8238 foreach id [array names idtags] {
8239 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8240 [llength $allparents($id)] == 1} {
8241 set a [lindex $arcnos($id) 0]
8242 if {$arctags($a) eq {}} {
8247 foreach id [array names idheads] {
8248 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8249 [llength $allparents($id)] == 1} {
8250 set a [lindex $arcnos($id) 0]
8251 if {$archeads($a) eq {}} {
8256 foreach id [lsort -unique $possible_seeds] {
8257 if {$arcnos($id) eq {}} {
8263 while {[incr a] <= $lim} {
8265 if {[llength $line] != 3} {error "bad line"}
8266 set s [lindex $line 0]
8268 lappend arcout($s) $a
8269 if {![info exists arcnos($s)]} {
8270 lappend possible_seeds $s
8273 set e [lindex $line 1]
8278 if {![info exists arcout($e)]} {
8282 set arcids($a) [lindex $line 2]
8283 foreach id $arcids($a) {
8284 lappend allparents($s) $id
8286 lappend arcnos($id) $a
8288 if {![info exists allparents($s)]} {
8289 set allparents($s) {}
8294 set nextarc [expr {$a - 1}]
8307 global nextarc cachedarcs possible_seeds
8311 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
8312 # make sure it's an integer
8313 set cachedarcs [expr {int([lindex $line 1])}]
8314 if {$cachedarcs < 0} {error "bad number of arcs"}
8316 set possible_seeds {}
8324 proc dropcache {err} {
8325 global allcwait nextarc cachedarcs seeds
8327 #puts "dropping cache ($err)"
8328 foreach v {arcnos arcout arcids arcstart arcend growing \
8329 arctags archeads allparents allchildren} {
8340 proc writecache {f} {
8341 global cachearc cachedarcs allccache
8342 global arcstart arcend arcnos arcids arcout
8346 if {$lim - $a > 1000} {
8347 set lim [expr {$a + 1000}]
8350 while {[incr a] <= $lim} {
8351 if {[info exists arcend($a)]} {
8352 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
8354 puts $f [list $arcstart($a) {} $arcids($a)]
8359 catch {file delete $allccache}
8360 #puts "writing cache failed ($err)"
8363 set cachearc [expr {$a - 1}]
8364 if {$a > $cachedarcs} {
8373 global nextarc cachedarcs cachearc allccache
8375 if {$nextarc == $cachedarcs} return
8377 set cachedarcs $nextarc
8379 set f [open $allccache w]
8380 puts $f [list 1 $cachedarcs]
8385 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
8386 # or 0 if neither is true.
8387 proc anc_or_desc {a b} {
8388 global arcout arcstart arcend arcnos cached_isanc
8390 if {$arcnos($a) eq $arcnos($b)} {
8391 # Both are on the same arc(s); either both are the same BMP,
8392 # or if one is not a BMP, the other is also not a BMP or is
8393 # the BMP at end of the arc (and it only has 1 incoming arc).
8394 # Or both can be BMPs with no incoming arcs.
8395 if {$a eq $b || $arcnos($a) eq {}} {
8398 # assert {[llength $arcnos($a)] == 1}
8399 set arc [lindex $arcnos($a) 0]
8400 set i [lsearch -exact $arcids($arc) $a]
8401 set j [lsearch -exact $arcids($arc) $b]
8402 if {$i < 0 || $i > $j} {
8409 if {![info exists arcout($a)]} {
8410 set arc [lindex $arcnos($a) 0]
8411 if {[info exists arcend($arc)]} {
8412 set aend $arcend($arc)
8416 set a $arcstart($arc)
8420 if {![info exists arcout($b)]} {
8421 set arc [lindex $arcnos($b) 0]
8422 if {[info exists arcend($arc)]} {
8423 set bend $arcend($arc)
8427 set b $arcstart($arc)
8437 if {[info exists cached_isanc($a,$bend)]} {
8438 if {$cached_isanc($a,$bend)} {
8442 if {[info exists cached_isanc($b,$aend)]} {
8443 if {$cached_isanc($b,$aend)} {
8446 if {[info exists cached_isanc($a,$bend)]} {
8451 set todo [list $a $b]
8454 for {set i 0} {$i < [llength $todo]} {incr i} {
8455 set x [lindex $todo $i]
8456 if {$anc($x) eq {}} {
8459 foreach arc $arcnos($x) {
8460 set xd $arcstart($arc)
8462 set cached_isanc($a,$bend) 1
8463 set cached_isanc($b,$aend) 0
8465 } elseif {$xd eq $aend} {
8466 set cached_isanc($b,$aend) 1
8467 set cached_isanc($a,$bend) 0
8470 if {![info exists anc($xd)]} {
8471 set anc($xd) $anc($x)
8473 } elseif {$anc($xd) ne $anc($x)} {
8478 set cached_isanc($a,$bend) 0
8479 set cached_isanc($b,$aend) 0
8483 # This identifies whether $desc has an ancestor that is
8484 # a growing tip of the graph and which is not an ancestor of $anc
8485 # and returns 0 if so and 1 if not.
8486 # If we subsequently discover a tag on such a growing tip, and that
8487 # turns out to be a descendent of $anc (which it could, since we
8488 # don't necessarily see children before parents), then $desc
8489 # isn't a good choice to display as a descendent tag of
8490 # $anc (since it is the descendent of another tag which is
8491 # a descendent of $anc). Similarly, $anc isn't a good choice to
8492 # display as a ancestor tag of $desc.
8494 proc is_certain {desc anc} {
8495 global arcnos arcout arcstart arcend growing problems
8498 if {[llength $arcnos($anc)] == 1} {
8499 # tags on the same arc are certain
8500 if {$arcnos($desc) eq $arcnos($anc)} {
8503 if {![info exists arcout($anc)]} {
8504 # if $anc is partway along an arc, use the start of the arc instead
8505 set a [lindex $arcnos($anc) 0]
8506 set anc $arcstart($a)
8509 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
8512 set a [lindex $arcnos($desc) 0]
8518 set anclist [list $x]
8522 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
8523 set x [lindex $anclist $i]
8528 foreach a $arcout($x) {
8529 if {[info exists growing($a)]} {
8530 if {![info exists growanc($x)] && $dl($x)} {
8536 if {[info exists dl($y)]} {
8540 if {![info exists done($y)]} {
8543 if {[info exists growanc($x)]} {
8547 for {set k 0} {$k < [llength $xl]} {incr k} {
8548 set z [lindex $xl $k]
8549 foreach c $arcout($z) {
8550 if {[info exists arcend($c)]} {
8552 if {[info exists dl($v)] && $dl($v)} {
8554 if {![info exists done($v)]} {
8557 if {[info exists growanc($v)]} {
8567 } elseif {$y eq $anc || !$dl($x)} {
8578 foreach x [array names growanc] {
8587 proc validate_arctags {a} {
8588 global arctags idtags
8592 foreach id $arctags($a) {
8594 if {![info exists idtags($id)]} {
8595 set na [lreplace $na $i $i]
8602 proc validate_archeads {a} {
8603 global archeads idheads
8606 set na $archeads($a)
8607 foreach id $archeads($a) {
8609 if {![info exists idheads($id)]} {
8610 set na [lreplace $na $i $i]
8614 set archeads($a) $na
8617 # Return the list of IDs that have tags that are descendents of id,
8618 # ignoring IDs that are descendents of IDs already reported.
8619 proc desctags {id} {
8620 global arcnos arcstart arcids arctags idtags allparents
8621 global growing cached_dtags
8623 if {![info exists allparents($id)]} {
8626 set t1 [clock clicks -milliseconds]
8628 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8629 # part-way along an arc; check that arc first
8630 set a [lindex $arcnos($id) 0]
8631 if {$arctags($a) ne {}} {
8633 set i [lsearch -exact $arcids($a) $id]
8635 foreach t $arctags($a) {
8636 set j [lsearch -exact $arcids($a) $t]
8644 set id $arcstart($a)
8645 if {[info exists idtags($id)]} {
8649 if {[info exists cached_dtags($id)]} {
8650 return $cached_dtags($id)
8657 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8658 set id [lindex $todo $i]
8660 set ta [info exists hastaggedancestor($id)]
8664 # ignore tags on starting node
8665 if {!$ta && $i > 0} {
8666 if {[info exists idtags($id)]} {
8669 } elseif {[info exists cached_dtags($id)]} {
8670 set tagloc($id) $cached_dtags($id)
8674 foreach a $arcnos($id) {
8676 if {!$ta && $arctags($a) ne {}} {
8678 if {$arctags($a) ne {}} {
8679 lappend tagloc($id) [lindex $arctags($a) end]
8682 if {$ta || $arctags($a) ne {}} {
8683 set tomark [list $d]
8684 for {set j 0} {$j < [llength $tomark]} {incr j} {
8685 set dd [lindex $tomark $j]
8686 if {![info exists hastaggedancestor($dd)]} {
8687 if {[info exists done($dd)]} {
8688 foreach b $arcnos($dd) {
8689 lappend tomark $arcstart($b)
8691 if {[info exists tagloc($dd)]} {
8694 } elseif {[info exists queued($dd)]} {
8697 set hastaggedancestor($dd) 1
8701 if {![info exists queued($d)]} {
8704 if {![info exists hastaggedancestor($d)]} {
8711 foreach id [array names tagloc] {
8712 if {![info exists hastaggedancestor($id)]} {
8713 foreach t $tagloc($id) {
8714 if {[lsearch -exact $tags $t] < 0} {
8720 set t2 [clock clicks -milliseconds]
8723 # remove tags that are descendents of other tags
8724 for {set i 0} {$i < [llength $tags]} {incr i} {
8725 set a [lindex $tags $i]
8726 for {set j 0} {$j < $i} {incr j} {
8727 set b [lindex $tags $j]
8728 set r [anc_or_desc $a $b]
8730 set tags [lreplace $tags $j $j]
8733 } elseif {$r == -1} {
8734 set tags [lreplace $tags $i $i]
8741 if {[array names growing] ne {}} {
8742 # graph isn't finished, need to check if any tag could get
8743 # eclipsed by another tag coming later. Simply ignore any
8744 # tags that could later get eclipsed.
8747 if {[is_certain $t $origid]} {
8751 if {$tags eq $ctags} {
8752 set cached_dtags($origid) $tags
8757 set cached_dtags($origid) $tags
8759 set t3 [clock clicks -milliseconds]
8760 if {0 && $t3 - $t1 >= 100} {
8761 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
8762 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8768 global arcnos arcids arcout arcend arctags idtags allparents
8769 global growing cached_atags
8771 if {![info exists allparents($id)]} {
8774 set t1 [clock clicks -milliseconds]
8776 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8777 # part-way along an arc; check that arc first
8778 set a [lindex $arcnos($id) 0]
8779 if {$arctags($a) ne {}} {
8781 set i [lsearch -exact $arcids($a) $id]
8782 foreach t $arctags($a) {
8783 set j [lsearch -exact $arcids($a) $t]
8789 if {![info exists arcend($a)]} {
8793 if {[info exists idtags($id)]} {
8797 if {[info exists cached_atags($id)]} {
8798 return $cached_atags($id)
8806 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8807 set id [lindex $todo $i]
8809 set td [info exists hastaggeddescendent($id)]
8813 # ignore tags on starting node
8814 if {!$td && $i > 0} {
8815 if {[info exists idtags($id)]} {
8818 } elseif {[info exists cached_atags($id)]} {
8819 set tagloc($id) $cached_atags($id)
8823 foreach a $arcout($id) {
8824 if {!$td && $arctags($a) ne {}} {
8826 if {$arctags($a) ne {}} {
8827 lappend tagloc($id) [lindex $arctags($a) 0]
8830 if {![info exists arcend($a)]} continue
8832 if {$td || $arctags($a) ne {}} {
8833 set tomark [list $d]
8834 for {set j 0} {$j < [llength $tomark]} {incr j} {
8835 set dd [lindex $tomark $j]
8836 if {![info exists hastaggeddescendent($dd)]} {
8837 if {[info exists done($dd)]} {
8838 foreach b $arcout($dd) {
8839 if {[info exists arcend($b)]} {
8840 lappend tomark $arcend($b)
8843 if {[info exists tagloc($dd)]} {
8846 } elseif {[info exists queued($dd)]} {
8849 set hastaggeddescendent($dd) 1
8853 if {![info exists queued($d)]} {
8856 if {![info exists hastaggeddescendent($d)]} {
8862 set t2 [clock clicks -milliseconds]
8865 foreach id [array names tagloc] {
8866 if {![info exists hastaggeddescendent($id)]} {
8867 foreach t $tagloc($id) {
8868 if {[lsearch -exact $tags $t] < 0} {
8875 # remove tags that are ancestors of other tags
8876 for {set i 0} {$i < [llength $tags]} {incr i} {
8877 set a [lindex $tags $i]
8878 for {set j 0} {$j < $i} {incr j} {
8879 set b [lindex $tags $j]
8880 set r [anc_or_desc $a $b]
8882 set tags [lreplace $tags $j $j]
8885 } elseif {$r == 1} {
8886 set tags [lreplace $tags $i $i]
8893 if {[array names growing] ne {}} {
8894 # graph isn't finished, need to check if any tag could get
8895 # eclipsed by another tag coming later. Simply ignore any
8896 # tags that could later get eclipsed.
8899 if {[is_certain $origid $t]} {
8903 if {$tags eq $ctags} {
8904 set cached_atags($origid) $tags
8909 set cached_atags($origid) $tags
8911 set t3 [clock clicks -milliseconds]
8912 if {0 && $t3 - $t1 >= 100} {
8913 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
8914 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8919 # Return the list of IDs that have heads that are descendents of id,
8920 # including id itself if it has a head.
8921 proc descheads {id} {
8922 global arcnos arcstart arcids archeads idheads cached_dheads
8925 if {![info exists allparents($id)]} {
8929 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8930 # part-way along an arc; check it first
8931 set a [lindex $arcnos($id) 0]
8932 if {$archeads($a) ne {}} {
8933 validate_archeads $a
8934 set i [lsearch -exact $arcids($a) $id]
8935 foreach t $archeads($a) {
8936 set j [lsearch -exact $arcids($a) $t]
8941 set id $arcstart($a)
8947 for {set i 0} {$i < [llength $todo]} {incr i} {
8948 set id [lindex $todo $i]
8949 if {[info exists cached_dheads($id)]} {
8950 set ret [concat $ret $cached_dheads($id)]
8952 if {[info exists idheads($id)]} {
8955 foreach a $arcnos($id) {
8956 if {$archeads($a) ne {}} {
8957 validate_archeads $a
8958 if {$archeads($a) ne {}} {
8959 set ret [concat $ret $archeads($a)]
8963 if {![info exists seen($d)]} {
8970 set ret [lsort -unique $ret]
8971 set cached_dheads($origid) $ret
8972 return [concat $ret $aret]
8975 proc addedtag {id} {
8976 global arcnos arcout cached_dtags cached_atags
8978 if {![info exists arcnos($id)]} return
8979 if {![info exists arcout($id)]} {
8980 recalcarc [lindex $arcnos($id) 0]
8982 catch {unset cached_dtags}
8983 catch {unset cached_atags}
8986 proc addedhead {hid head} {
8987 global arcnos arcout cached_dheads
8989 if {![info exists arcnos($hid)]} return
8990 if {![info exists arcout($hid)]} {
8991 recalcarc [lindex $arcnos($hid) 0]
8993 catch {unset cached_dheads}
8996 proc removedhead {hid head} {
8997 global cached_dheads
8999 catch {unset cached_dheads}
9002 proc movedhead {hid head} {
9003 global arcnos arcout cached_dheads
9005 if {![info exists arcnos($hid)]} return
9006 if {![info exists arcout($hid)]} {
9007 recalcarc [lindex $arcnos($hid) 0]
9009 catch {unset cached_dheads}
9012 proc changedrefs {} {
9013 global cached_dheads cached_dtags cached_atags
9014 global arctags archeads arcnos arcout idheads idtags
9016 foreach id [concat [array names idheads] [array names idtags]] {
9017 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9018 set a [lindex $arcnos($id) 0]
9019 if {![info exists donearc($a)]} {
9025 catch {unset cached_dtags}
9026 catch {unset cached_atags}
9027 catch {unset cached_dheads}
9030 proc rereadrefs {} {
9031 global idtags idheads idotherrefs mainheadid
9033 set refids [concat [array names idtags] \
9034 [array names idheads] [array names idotherrefs]]
9035 foreach id $refids {
9036 if {![info exists ref($id)]} {
9037 set ref($id) [listrefs $id]
9040 set oldmainhead $mainheadid
9043 set refids [lsort -unique [concat $refids [array names idtags] \
9044 [array names idheads] [array names idotherrefs]]]
9045 foreach id $refids {
9046 set v [listrefs $id]
9047 if {![info exists ref($id)] || $ref($id) != $v} {
9051 if {$oldmainhead ne $mainheadid} {
9052 redrawtags $oldmainhead
9053 redrawtags $mainheadid
9058 proc listrefs {id} {
9059 global idtags idheads idotherrefs
9062 if {[info exists idtags($id)]} {
9066 if {[info exists idheads($id)]} {
9070 if {[info exists idotherrefs($id)]} {
9071 set z $idotherrefs($id)
9073 return [list $x $y $z]
9076 proc showtag {tag isnew} {
9077 global ctext tagcontents tagids linknum tagobjid
9080 addtohistory [list showtag $tag 0]
9082 $ctext conf -state normal
9086 if {![info exists tagcontents($tag)]} {
9088 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9091 if {[info exists tagcontents($tag)]} {
9092 set text $tagcontents($tag)
9094 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9096 appendwithlinks $text {}
9097 $ctext conf -state disabled
9109 if {[info exists gitktmpdir]} {
9110 catch {file delete -force $gitktmpdir}
9114 proc mkfontdisp {font top which} {
9115 global fontattr fontpref $font
9117 set fontpref($font) [set $font]
9118 button $top.${font}but -text $which -font optionfont \
9119 -command [list choosefont $font $which]
9120 label $top.$font -relief flat -font $font \
9121 -text $fontattr($font,family) -justify left
9122 grid x $top.${font}but $top.$font -sticky w
9125 proc choosefont {font which} {
9126 global fontparam fontlist fonttop fontattr
9128 set fontparam(which) $which
9129 set fontparam(font) $font
9130 set fontparam(family) [font actual $font -family]
9131 set fontparam(size) $fontattr($font,size)
9132 set fontparam(weight) $fontattr($font,weight)
9133 set fontparam(slant) $fontattr($font,slant)
9136 if {![winfo exists $top]} {
9138 eval font config sample [font actual $font]
9140 wm title $top [mc "Gitk font chooser"]
9141 label $top.l -textvariable fontparam(which)
9142 pack $top.l -side top
9143 set fontlist [lsort [font families]]
9145 listbox $top.f.fam -listvariable fontlist \
9146 -yscrollcommand [list $top.f.sb set]
9147 bind $top.f.fam <<ListboxSelect>> selfontfam
9148 scrollbar $top.f.sb -command [list $top.f.fam yview]
9149 pack $top.f.sb -side right -fill y
9150 pack $top.f.fam -side left -fill both -expand 1
9151 pack $top.f -side top -fill both -expand 1
9153 spinbox $top.g.size -from 4 -to 40 -width 4 \
9154 -textvariable fontparam(size) \
9155 -validatecommand {string is integer -strict %s}
9156 checkbutton $top.g.bold -padx 5 \
9157 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9158 -variable fontparam(weight) -onvalue bold -offvalue normal
9159 checkbutton $top.g.ital -padx 5 \
9160 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9161 -variable fontparam(slant) -onvalue italic -offvalue roman
9162 pack $top.g.size $top.g.bold $top.g.ital -side left
9163 pack $top.g -side top
9164 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9166 $top.c create text 100 25 -anchor center -text $which -font sample \
9167 -fill black -tags text
9168 bind $top.c <Configure> [list centertext $top.c]
9169 pack $top.c -side top -fill x
9171 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9172 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9173 grid $top.buts.ok $top.buts.can
9174 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9175 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9176 pack $top.buts -side bottom -fill x
9177 trace add variable fontparam write chg_fontparam
9180 $top.c itemconf text -text $which
9182 set i [lsearch -exact $fontlist $fontparam(family)]
9184 $top.f.fam selection set $i
9189 proc centertext {w} {
9190 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9194 global fontparam fontpref prefstop
9196 set f $fontparam(font)
9197 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9198 if {$fontparam(weight) eq "bold"} {
9199 lappend fontpref($f) "bold"
9201 if {$fontparam(slant) eq "italic"} {
9202 lappend fontpref($f) "italic"
9205 $w conf -text $fontparam(family) -font $fontpref($f)
9211 global fonttop fontparam
9213 if {[info exists fonttop]} {
9214 catch {destroy $fonttop}
9215 catch {font delete sample}
9221 proc selfontfam {} {
9222 global fonttop fontparam
9224 set i [$fonttop.f.fam curselection]
9226 set fontparam(family) [$fonttop.f.fam get $i]
9230 proc chg_fontparam {v sub op} {
9233 font config sample -$sub $fontparam($sub)
9237 global maxwidth maxgraphpct
9238 global oldprefs prefstop showneartags showlocalchanges
9239 global bgcolor fgcolor ctext diffcolors selectbgcolor
9240 global tabstop limitdiffs autoselect extdifftool
9244 if {[winfo exists $top]} {
9248 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9249 limitdiffs tabstop} {
9250 set oldprefs($v) [set $v]
9253 wm title $top [mc "Gitk preferences"]
9254 label $top.ldisp -text [mc "Commit list display options"]
9255 grid $top.ldisp - -sticky w -pady 10
9256 label $top.spacer -text " "
9257 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9259 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9260 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9261 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9263 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9264 grid x $top.maxpctl $top.maxpct -sticky w
9265 frame $top.showlocal
9266 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9267 checkbutton $top.showlocal.b -variable showlocalchanges
9268 pack $top.showlocal.b $top.showlocal.l -side left
9269 grid x $top.showlocal -sticky w
9270 frame $top.autoselect
9271 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9272 checkbutton $top.autoselect.b -variable autoselect
9273 pack $top.autoselect.b $top.autoselect.l -side left
9274 grid x $top.autoselect -sticky w
9276 label $top.ddisp -text [mc "Diff display options"]
9277 grid $top.ddisp - -sticky w -pady 10
9278 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9279 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9280 grid x $top.tabstopl $top.tabstop -sticky w
9282 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9283 checkbutton $top.ntag.b -variable showneartags
9284 pack $top.ntag.b $top.ntag.l -side left
9285 grid x $top.ntag -sticky w
9287 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9288 checkbutton $top.ldiff.b -variable limitdiffs
9289 pack $top.ldiff.b $top.ldiff.l -side left
9290 grid x $top.ldiff -sticky w
9292 entry $top.extdifft -textvariable extdifftool
9294 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
9296 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
9297 -command choose_extdiff
9298 pack $top.extdifff.l $top.extdifff.b -side left
9299 grid x $top.extdifff $top.extdifft -sticky w
9301 label $top.cdisp -text [mc "Colors: press to choose"]
9302 grid $top.cdisp - -sticky w -pady 10
9303 label $top.bg -padx 40 -relief sunk -background $bgcolor
9304 button $top.bgbut -text [mc "Background"] -font optionfont \
9305 -command [list choosecolor bgcolor {} $top.bg background setbg]
9306 grid x $top.bgbut $top.bg -sticky w
9307 label $top.fg -padx 40 -relief sunk -background $fgcolor
9308 button $top.fgbut -text [mc "Foreground"] -font optionfont \
9309 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
9310 grid x $top.fgbut $top.fg -sticky w
9311 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
9312 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
9313 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
9314 [list $ctext tag conf d0 -foreground]]
9315 grid x $top.diffoldbut $top.diffold -sticky w
9316 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
9317 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
9318 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
9319 [list $ctext tag conf d1 -foreground]]
9320 grid x $top.diffnewbut $top.diffnew -sticky w
9321 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
9322 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
9323 -command [list choosecolor diffcolors 2 $top.hunksep \
9324 "diff hunk header" \
9325 [list $ctext tag conf hunksep -foreground]]
9326 grid x $top.hunksepbut $top.hunksep -sticky w
9327 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
9328 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
9329 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
9330 grid x $top.selbgbut $top.selbgsep -sticky w
9332 label $top.cfont -text [mc "Fonts: press to choose"]
9333 grid $top.cfont - -sticky w -pady 10
9334 mkfontdisp mainfont $top [mc "Main font"]
9335 mkfontdisp textfont $top [mc "Diff display font"]
9336 mkfontdisp uifont $top [mc "User interface font"]
9339 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
9340 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
9341 grid $top.buts.ok $top.buts.can
9342 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9343 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9344 grid $top.buts - - -pady 10 -sticky ew
9345 bind $top <Visibility> "focus $top.buts.ok"
9348 proc choose_extdiff {} {
9351 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
9353 set extdifftool $prog
9357 proc choosecolor {v vi w x cmd} {
9360 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
9361 -title [mc "Gitk: choose color for %s" $x]]
9362 if {$c eq {}} return
9363 $w conf -background $c
9369 global bglist cflist
9371 $w configure -selectbackground $c
9373 $cflist tag configure highlight \
9374 -background [$cflist cget -selectbackground]
9375 allcanvs itemconf secsel -fill $c
9382 $w conf -background $c
9390 $w conf -foreground $c
9392 allcanvs itemconf text -fill $c
9393 $canv itemconf circle -outline $c
9397 global oldprefs prefstop
9399 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9400 limitdiffs tabstop} {
9402 set $v $oldprefs($v)
9404 catch {destroy $prefstop}
9410 global maxwidth maxgraphpct
9411 global oldprefs prefstop showneartags showlocalchanges
9412 global fontpref mainfont textfont uifont
9413 global limitdiffs treediffs
9415 catch {destroy $prefstop}
9419 if {$mainfont ne $fontpref(mainfont)} {
9420 set mainfont $fontpref(mainfont)
9421 parsefont mainfont $mainfont
9422 eval font configure mainfont [fontflags mainfont]
9423 eval font configure mainfontbold [fontflags mainfont 1]
9427 if {$textfont ne $fontpref(textfont)} {
9428 set textfont $fontpref(textfont)
9429 parsefont textfont $textfont
9430 eval font configure textfont [fontflags textfont]
9431 eval font configure textfontbold [fontflags textfont 1]
9433 if {$uifont ne $fontpref(uifont)} {
9434 set uifont $fontpref(uifont)
9435 parsefont uifont $uifont
9436 eval font configure uifont [fontflags uifont]
9439 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
9440 if {$showlocalchanges} {
9446 if {$limitdiffs != $oldprefs(limitdiffs)} {
9447 # treediffs elements are limited by path
9448 catch {unset treediffs}
9450 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
9451 || $maxgraphpct != $oldprefs(maxgraphpct)} {
9453 } elseif {$showneartags != $oldprefs(showneartags) ||
9454 $limitdiffs != $oldprefs(limitdiffs)} {
9459 proc formatdate {d} {
9460 global datetimeformat
9462 set d [clock format $d -format $datetimeformat]
9467 # This list of encoding names and aliases is distilled from
9468 # http://www.iana.org/assignments/character-sets.
9469 # Not all of them are supported by Tcl.
9470 set encoding_aliases {
9471 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
9472 ISO646-US US-ASCII us IBM367 cp367 csASCII }
9473 { ISO-10646-UTF-1 csISO10646UTF1 }
9474 { ISO_646.basic:1983 ref csISO646basic1983 }
9475 { INVARIANT csINVARIANT }
9476 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
9477 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
9478 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
9479 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
9480 { NATS-DANO iso-ir-9-1 csNATSDANO }
9481 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
9482 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
9483 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
9484 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
9485 { ISO-2022-KR csISO2022KR }
9487 { ISO-2022-JP csISO2022JP }
9488 { ISO-2022-JP-2 csISO2022JP2 }
9489 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
9491 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
9492 { IT iso-ir-15 ISO646-IT csISO15Italian }
9493 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
9494 { ES iso-ir-17 ISO646-ES csISO17Spanish }
9495 { greek7-old iso-ir-18 csISO18Greek7Old }
9496 { latin-greek iso-ir-19 csISO19LatinGreek }
9497 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
9498 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
9499 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
9500 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
9501 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
9502 { BS_viewdata iso-ir-47 csISO47BSViewdata }
9503 { INIS iso-ir-49 csISO49INIS }
9504 { INIS-8 iso-ir-50 csISO50INIS8 }
9505 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
9506 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
9507 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
9508 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
9509 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
9510 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
9512 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
9513 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
9514 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
9515 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
9516 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
9517 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
9518 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
9519 { greek7 iso-ir-88 csISO88Greek7 }
9520 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
9521 { iso-ir-90 csISO90 }
9522 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
9523 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
9524 csISO92JISC62991984b }
9525 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
9526 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
9527 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
9528 csISO95JIS62291984handadd }
9529 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
9530 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
9531 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
9532 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
9534 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
9535 { T.61-7bit iso-ir-102 csISO102T617bit }
9536 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
9537 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
9538 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
9539 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
9540 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
9541 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
9542 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
9543 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
9544 arabic csISOLatinArabic }
9545 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
9546 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
9547 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
9548 greek greek8 csISOLatinGreek }
9549 { T.101-G2 iso-ir-128 csISO128T101G2 }
9550 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
9552 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
9553 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
9554 { CSN_369103 iso-ir-139 csISO139CSN369103 }
9555 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
9556 { ISO_6937-2-add iso-ir-142 csISOTextComm }
9557 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
9558 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
9559 csISOLatinCyrillic }
9560 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
9561 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
9562 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
9563 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
9564 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
9565 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
9566 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
9567 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
9568 { ISO_10367-box iso-ir-155 csISO10367Box }
9569 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
9570 { latin-lap lap iso-ir-158 csISO158Lap }
9571 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
9572 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
9575 { JIS_X0201 X0201 csHalfWidthKatakana }
9576 { KSC5636 ISO646-KR csKSC5636 }
9577 { ISO-10646-UCS-2 csUnicode }
9578 { ISO-10646-UCS-4 csUCS4 }
9579 { DEC-MCS dec csDECMCS }
9580 { hp-roman8 roman8 r8 csHPRoman8 }
9581 { macintosh mac csMacintosh }
9582 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
9584 { IBM038 EBCDIC-INT cp038 csIBM038 }
9585 { IBM273 CP273 csIBM273 }
9586 { IBM274 EBCDIC-BE CP274 csIBM274 }
9587 { IBM275 EBCDIC-BR cp275 csIBM275 }
9588 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
9589 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
9590 { IBM280 CP280 ebcdic-cp-it csIBM280 }
9591 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
9592 { IBM284 CP284 ebcdic-cp-es csIBM284 }
9593 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
9594 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
9595 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
9596 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
9597 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
9598 { IBM424 cp424 ebcdic-cp-he csIBM424 }
9599 { IBM437 cp437 437 csPC8CodePage437 }
9600 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
9601 { IBM775 cp775 csPC775Baltic }
9602 { IBM850 cp850 850 csPC850Multilingual }
9603 { IBM851 cp851 851 csIBM851 }
9604 { IBM852 cp852 852 csPCp852 }
9605 { IBM855 cp855 855 csIBM855 }
9606 { IBM857 cp857 857 csIBM857 }
9607 { IBM860 cp860 860 csIBM860 }
9608 { IBM861 cp861 861 cp-is csIBM861 }
9609 { IBM862 cp862 862 csPC862LatinHebrew }
9610 { IBM863 cp863 863 csIBM863 }
9611 { IBM864 cp864 csIBM864 }
9612 { IBM865 cp865 865 csIBM865 }
9613 { IBM866 cp866 866 csIBM866 }
9614 { IBM868 CP868 cp-ar csIBM868 }
9615 { IBM869 cp869 869 cp-gr csIBM869 }
9616 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
9617 { IBM871 CP871 ebcdic-cp-is csIBM871 }
9618 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
9619 { IBM891 cp891 csIBM891 }
9620 { IBM903 cp903 csIBM903 }
9621 { IBM904 cp904 904 csIBBM904 }
9622 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
9623 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
9624 { IBM1026 CP1026 csIBM1026 }
9625 { EBCDIC-AT-DE csIBMEBCDICATDE }
9626 { EBCDIC-AT-DE-A csEBCDICATDEA }
9627 { EBCDIC-CA-FR csEBCDICCAFR }
9628 { EBCDIC-DK-NO csEBCDICDKNO }
9629 { EBCDIC-DK-NO-A csEBCDICDKNOA }
9630 { EBCDIC-FI-SE csEBCDICFISE }
9631 { EBCDIC-FI-SE-A csEBCDICFISEA }
9632 { EBCDIC-FR csEBCDICFR }
9633 { EBCDIC-IT csEBCDICIT }
9634 { EBCDIC-PT csEBCDICPT }
9635 { EBCDIC-ES csEBCDICES }
9636 { EBCDIC-ES-A csEBCDICESA }
9637 { EBCDIC-ES-S csEBCDICESS }
9638 { EBCDIC-UK csEBCDICUK }
9639 { EBCDIC-US csEBCDICUS }
9640 { UNKNOWN-8BIT csUnknown8BiT }
9641 { MNEMONIC csMnemonic }
9646 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
9647 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
9648 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
9649 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
9650 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
9651 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
9652 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
9653 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
9654 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
9655 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
9656 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
9657 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
9658 { IBM1047 IBM-1047 }
9659 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
9660 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
9661 { UNICODE-1-1 csUnicode11 }
9664 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
9665 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
9667 { ISO-8859-15 ISO_8859-15 Latin-9 }
9668 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
9669 { GBK CP936 MS936 windows-936 }
9670 { JIS_Encoding csJISEncoding }
9671 { Shift_JIS MS_Kanji csShiftJIS }
9672 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
9674 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
9675 { ISO-10646-UCS-Basic csUnicodeASCII }
9676 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
9677 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
9678 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
9679 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
9680 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
9681 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
9682 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
9683 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
9684 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
9685 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
9686 { Adobe-Standard-Encoding csAdobeStandardEncoding }
9687 { Ventura-US csVenturaUS }
9688 { Ventura-International csVenturaInternational }
9689 { PC8-Danish-Norwegian csPC8DanishNorwegian }
9690 { PC8-Turkish csPC8Turkish }
9691 { IBM-Symbols csIBMSymbols }
9692 { IBM-Thai csIBMThai }
9693 { HP-Legal csHPLegal }
9694 { HP-Pi-font csHPPiFont }
9695 { HP-Math8 csHPMath8 }
9696 { Adobe-Symbol-Encoding csHPPSMath }
9697 { HP-DeskTop csHPDesktop }
9698 { Ventura-Math csVenturaMath }
9699 { Microsoft-Publishing csMicrosoftPublishing }
9700 { Windows-31J csWindows31J }
9705 proc tcl_encoding {enc} {
9706 global encoding_aliases
9707 set names [encoding names]
9708 set lcnames [string tolower $names]
9709 set enc [string tolower $enc]
9710 set i [lsearch -exact $lcnames $enc]
9712 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
9713 if {[regsub {^iso[-_]} $enc iso encx]} {
9714 set i [lsearch -exact $lcnames $encx]
9718 foreach l $encoding_aliases {
9719 set ll [string tolower $l]
9720 if {[lsearch -exact $ll $enc] < 0} continue
9721 # look through the aliases for one that tcl knows about
9723 set i [lsearch -exact $lcnames $e]
9725 if {[regsub {^iso[-_]} $e iso ex]} {
9726 set i [lsearch -exact $lcnames $ex]
9735 return [lindex $names $i]
9740 # First check that Tcl/Tk is recent enough
9741 if {[catch {package require Tk 8.4} err]} {
9742 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
9743 Gitk requires at least Tcl/Tk 8.4."]
9748 set wrcomcmd "git diff-tree --stdin -p --pretty"
9752 set gitencoding [exec git config --get i18n.commitencoding]
9754 if {$gitencoding == ""} {
9755 set gitencoding "utf-8"
9757 set tclencoding [tcl_encoding $gitencoding]
9758 if {$tclencoding == {}} {
9759 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
9762 set mainfont {Helvetica 9}
9763 set textfont {Courier 9}
9764 set uifont {Helvetica 9 bold}
9766 set findmergefiles 0
9774 set cmitmode "patch"
9775 set wrapcomment "none"
9779 set showlocalchanges 1
9781 set datetimeformat "%Y-%m-%d %H:%M:%S"
9784 set extdifftool "meld"
9786 set colors {green red blue magenta darkgrey brown orange}
9789 set diffcolors {red "#00a000" blue}
9792 set selectbgcolor gray85
9794 set circlecolors {white blue gray blue blue}
9796 ## For msgcat loading, first locate the installation location.
9797 if { [info exists ::env(GITK_MSGSDIR)] } {
9798 ## Msgsdir was manually set in the environment.
9799 set gitk_msgsdir $::env(GITK_MSGSDIR)
9801 ## Let's guess the prefix from argv0.
9802 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
9803 set gitk_libdir [file join $gitk_prefix share gitk lib]
9804 set gitk_msgsdir [file join $gitk_libdir msgs]
9808 ## Internationalization (i18n) through msgcat and gettext. See
9809 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
9810 package require msgcat
9811 namespace import ::msgcat::mc
9812 ## And eventually load the actual message catalog
9813 ::msgcat::mcload $gitk_msgsdir
9815 catch {source ~/.gitk}
9817 font create optionfont -family sans-serif -size -12
9819 parsefont mainfont $mainfont
9820 eval font create mainfont [fontflags mainfont]
9821 eval font create mainfontbold [fontflags mainfont 1]
9823 parsefont textfont $textfont
9824 eval font create textfont [fontflags textfont]
9825 eval font create textfontbold [fontflags textfont 1]
9827 parsefont uifont $uifont
9828 eval font create uifont [fontflags uifont]
9832 # check that we can find a .git directory somewhere...
9833 if {[catch {set gitdir [gitdir]}]} {
9834 show_error {} . [mc "Cannot find a git repository here."]
9837 if {![file isdirectory $gitdir]} {
9838 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
9843 set cmdline_files {}
9845 set revtreeargscmd {}
9847 switch -glob -- $arg {
9850 set cmdline_files [lrange $argv [expr {$i + 1}] end]
9854 set revtreeargscmd [string range $arg 10 end]
9857 lappend revtreeargs $arg
9863 if {$i >= [llength $argv] && $revtreeargs ne {}} {
9864 # no -- on command line, but some arguments (other than --argscmd)
9866 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
9867 set cmdline_files [split $f "\n"]
9868 set n [llength $cmdline_files]
9869 set revtreeargs [lrange $revtreeargs 0 end-$n]
9870 # Unfortunately git rev-parse doesn't produce an error when
9871 # something is both a revision and a filename. To be consistent
9872 # with git log and git rev-list, check revtreeargs for filenames.
9873 foreach arg $revtreeargs {
9874 if {[file exists $arg]} {
9875 show_error {} . [mc "Ambiguous argument '%s': both revision\
9881 # unfortunately we get both stdout and stderr in $err,
9882 # so look for "fatal:".
9883 set i [string first "fatal:" $err]
9885 set err [string range $err [expr {$i + 6}] end]
9887 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
9892 set nullid "0000000000000000000000000000000000000000"
9893 set nullid2 "0000000000000000000000000000000000000001"
9894 set nullfile "/dev/null"
9896 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
9903 set highlight_paths {}
9905 set searchdirn -forwards
9909 set markingmatches 0
9910 set linkentercount 0
9911 set need_redisplay 0
9918 set selectedhlview [mc "None"]
9919 set highlight_related [mc "None"]
9920 set highlight_files {}
9924 set viewargscmd(0) {}
9934 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
9937 # wait for the window to become visible
9939 wm title . "[file tail $argv0]: [file tail [pwd]]"
9942 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
9943 # create a view for the files/dirs specified on the command line
9947 set viewname(1) [mc "Command line"]
9948 set viewfiles(1) $cmdline_files
9949 set viewargs(1) $revtreeargs
9950 set viewargscmd(1) $revtreeargscmd
9954 .bar.view entryconf [mc "Edit view..."] -state normal
9955 .bar.view entryconf [mc "Delete view"] -state normal
9958 if {[info exists permviews]} {
9959 foreach v $permviews {
9962 set viewname($n) [lindex $v 0]
9963 set viewfiles($n) [lindex $v 1]
9964 set viewargs($n) [lindex $v 2]
9965 set viewargscmd($n) [lindex $v 3]