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.
25 global isonrunq runq currunq
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {} && ![info exists currunq]} {
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 {}
44 if {$runq eq {} && ![info exists currunq]} {
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]
63 global isonrunq runq currunq
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 currunq [lindex $runq 0]
71 set runq [lrange $runq 1 end]
72 set repeat [eval $script]
74 set t1 [clock clicks -milliseconds]
75 set t [expr {$t1 - $t0}]
76 if {$repeat ne {} && $repeat} {
77 if {$fd eq {} || $repeat == 2} {
78 # script returns 1 if it wants to be readded
79 # file readers return 2 if they could do more straight away
80 lappend runq [list $fd $script]
82 fileevent $fd readable [list filereadable $fd $script]
84 } elseif {$fd eq {}} {
85 unset isonrunq($script)
88 if {$t1 - $tstart >= 80} break
95 proc reg_instance {fd} {
96 global commfd leftover loginstance
98 set i [incr loginstance]
104 proc unmerged_files {files} {
107 # find the list of unmerged files
111 set fd [open "| git ls-files -u" r]
113 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
116 while {[gets $fd line] >= 0} {
117 set i [string first "\t" $line]
119 set fname [string range $line [expr {$i+1}] end]
120 if {[lsearch -exact $mlist $fname] >= 0} continue
122 if {$files eq {} || [path_filter $files $fname]} {
130 proc parseviewargs {n arglist} {
131 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
139 set origargs $arglist
143 foreach arg $arglist {
150 switch -glob -- $arg {
154 # remove from origargs in case we hit an unknown option
155 set origargs [lreplace $origargs $i $i]
158 # These request or affect diff output, which we don't want.
159 # Some could be used to set our defaults for diff display.
161 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
162 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
163 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
164 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
165 "--ignore-space-change" - "-U*" - "--unified=*" {
166 lappend diffargs $arg
168 # These cause our parsing of git log's output to fail, or else
169 # they're options we want to set ourselves, so ignore them.
170 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
171 "--name-only" - "--name-status" - "--color" - "--color-words" -
172 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
173 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
174 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
175 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
176 "--objects" - "--objects-edge" - "--reverse" {
178 # These are harmless, and some are even useful
179 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
180 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
181 "--full-history" - "--dense" - "--sparse" -
182 "--follow" - "--left-right" - "--encoding=*" {
185 # These mean that we get a subset of the commits
186 "--diff-filter=*" - "--no-merges" - "--unpacked" -
187 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
188 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
189 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
190 "--remove-empty" - "--first-parent" - "--cherry-pick" -
191 "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
195 # This appears to be the only one that has a value as a
196 # separate word following it
203 set notflag [expr {!$notflag}]
211 # git rev-parse doesn't understand --merge
212 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
214 # Other flag arguments including -<n>
216 if {[string is digit -strict [string range $arg 1 end]]} {
219 # a flag argument that we don't recognize;
220 # that means we can't optimize
225 # Non-flag arguments specify commits or ranges of commits
227 if {[string match "*...*" $arg]} {
228 lappend revargs --gitk-symmetric-diff-marker
234 set vdflags($n) $diffargs
235 set vflags($n) $glflags
236 set vrevs($n) $revargs
237 set vfiltered($n) $filtered
238 set vorigargs($n) $origargs
242 proc parseviewrevs {view revs} {
243 global vposids vnegids
248 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
249 # we get stdout followed by stderr in $err
250 # for an unknown rev, git rev-parse echoes it and then errors out
251 set errlines [split $err "\n"]
253 for {set l 0} {$l < [llength $errlines]} {incr l} {
254 set line [lindex $errlines $l]
255 if {!([string length $line] == 40 && [string is xdigit $line])} {
256 if {[string match "fatal:*" $line]} {
257 if {[string match "fatal: ambiguous argument*" $line]
259 if {[llength $badrev] == 1} {
260 set err "unknown revision $badrev"
262 set err "unknown revisions: [join $badrev ", "]"
265 set err [join [lrange $errlines $l end] "\n"]
272 error_popup "[mc "Error parsing revisions:"] $err"
279 foreach id [split $ids "\n"] {
280 if {$id eq "--gitk-symmetric-diff-marker"} {
282 } elseif {[string match "^*" $id]} {
289 lappend neg [string range $id 1 end]
294 lset ret end [lindex $ret end]...$id
300 set vposids($view) $pos
301 set vnegids($view) $neg
305 # Start off a git log process and arrange to read its output
306 proc start_rev_list {view} {
307 global startmsecs commitidx viewcomplete curview
309 global viewargs viewargscmd viewfiles vfilelimit
310 global showlocalchanges
311 global viewactive viewinstances vmergeonly
313 global vcanopt vflags vrevs vorigargs
315 set startmsecs [clock clicks -milliseconds]
316 set commitidx($view) 0
317 # these are set this way for the error exits
318 set viewcomplete($view) 1
319 set viewactive($view) 0
322 set args $viewargs($view)
323 if {$viewargscmd($view) ne {}} {
325 set str [exec sh -c $viewargscmd($view)]
327 error_popup "[mc "Error executing --argscmd command:"] $err"
330 set args [concat $args [split $str "\n"]]
332 set vcanopt($view) [parseviewargs $view $args]
334 set files $viewfiles($view)
335 if {$vmergeonly($view)} {
336 set files [unmerged_files $files]
339 if {$nr_unmerged == 0} {
340 error_popup [mc "No files selected: --merge specified but\
341 no files are unmerged."]
343 error_popup [mc "No files selected: --merge specified but\
344 no unmerged files are within file limit."]
349 set vfilelimit($view) $files
351 if {$vcanopt($view)} {
352 set revs [parseviewrevs $view $vrevs($view)]
356 set args [concat $vflags($view) $revs]
358 set args $vorigargs($view)
362 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
363 --boundary $args "--" $files] r]
365 error_popup "[mc "Error executing git log:"] $err"
368 set i [reg_instance $fd]
369 set viewinstances($view) [list $i]
370 if {$showlocalchanges && $mainheadid ne {}} {
371 interestedin $mainheadid dodiffindex
373 fconfigure $fd -blocking 0 -translation lf -eofchar {}
374 if {$tclencoding != {}} {
375 fconfigure $fd -encoding $tclencoding
377 filerun $fd [list getcommitlines $fd $i $view 0]
378 nowbusy $view [mc "Reading"]
379 set viewcomplete($view) 0
380 set viewactive($view) 1
384 proc stop_instance {inst} {
385 global commfd leftover
387 set fd $commfd($inst)
391 if {$::tcl_platform(platform) eq {windows}} {
400 unset leftover($inst)
403 proc stop_backends {} {
406 foreach inst [array names commfd] {
411 proc stop_rev_list {view} {
414 foreach inst $viewinstances($view) {
417 set viewinstances($view) {}
420 proc reset_pending_select {selid} {
421 global pending_select mainheadid selectheadid
424 set pending_select $selid
425 } elseif {$selectheadid ne {}} {
426 set pending_select $selectheadid
428 set pending_select $mainheadid
432 proc getcommits {selid} {
433 global canv curview need_redisplay viewactive
436 if {[start_rev_list $curview]} {
437 reset_pending_select $selid
438 show_status [mc "Reading commits..."]
441 show_status [mc "No commits selected"]
445 proc updatecommits {} {
446 global curview vcanopt vorigargs vfilelimit viewinstances
447 global viewactive viewcomplete tclencoding
448 global startmsecs showneartags showlocalchanges
449 global mainheadid pending_select
451 global varcid vposids vnegids vflags vrevs
453 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
454 set oldmainid $mainheadid
456 if {$showlocalchanges} {
457 if {$mainheadid ne $oldmainid} {
460 if {[commitinview $mainheadid $curview]} {
465 if {$vcanopt($view)} {
466 set oldpos $vposids($view)
467 set oldneg $vnegids($view)
468 set revs [parseviewrevs $view $vrevs($view)]
472 # note: getting the delta when negative refs change is hard,
473 # and could require multiple git log invocations, so in that
474 # case we ask git log for all the commits (not just the delta)
475 if {$oldneg eq $vnegids($view)} {
478 # take out positive refs that we asked for before or
479 # that we have already seen
481 if {[string length $rev] == 40} {
482 if {[lsearch -exact $oldpos $rev] < 0
483 && ![info exists varcid($view,$rev)]} {
488 lappend $newrevs $rev
491 if {$npos == 0} return
493 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
495 set args [concat $vflags($view) $revs --not $oldpos]
497 set args $vorigargs($view)
500 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
501 --boundary $args "--" $vfilelimit($view)] r]
503 error_popup "[mc "Error executing git log:"] $err"
506 if {$viewactive($view) == 0} {
507 set startmsecs [clock clicks -milliseconds]
509 set i [reg_instance $fd]
510 lappend viewinstances($view) $i
511 fconfigure $fd -blocking 0 -translation lf -eofchar {}
512 if {$tclencoding != {}} {
513 fconfigure $fd -encoding $tclencoding
515 filerun $fd [list getcommitlines $fd $i $view 1]
516 incr viewactive($view)
517 set viewcomplete($view) 0
518 reset_pending_select {}
519 nowbusy $view "Reading"
525 proc reloadcommits {} {
526 global curview viewcomplete selectedline currentid thickerline
527 global showneartags treediffs commitinterest cached_commitrow
531 if {$selectedline ne {}} {
535 if {!$viewcomplete($curview)} {
536 stop_rev_list $curview
540 catch {unset currentid}
541 catch {unset thickerline}
542 catch {unset treediffs}
549 catch {unset commitinterest}
550 catch {unset cached_commitrow}
551 catch {unset targetid}
557 # This makes a string representation of a positive integer which
558 # sorts as a string in numerical order
561 return [format "%x" $n]
562 } elseif {$n < 256} {
563 return [format "x%.2x" $n]
564 } elseif {$n < 65536} {
565 return [format "y%.4x" $n]
567 return [format "z%.8x" $n]
570 # Procedures used in reordering commits from git log (without
571 # --topo-order) into the order for display.
573 proc varcinit {view} {
574 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
575 global vtokmod varcmod vrowmod varcix vlastins
577 set varcstart($view) {{}}
578 set vupptr($view) {0}
579 set vdownptr($view) {0}
580 set vleftptr($view) {0}
581 set vbackptr($view) {0}
582 set varctok($view) {{}}
583 set varcrow($view) {{}}
584 set vtokmod($view) {}
587 set varcix($view) {{}}
588 set vlastins($view) {0}
591 proc resetvarcs {view} {
592 global varcid varccommits parents children vseedcount ordertok
594 foreach vid [array names varcid $view,*] {
599 # some commits might have children but haven't been seen yet
600 foreach vid [array names children $view,*] {
603 foreach va [array names varccommits $view,*] {
604 unset varccommits($va)
606 foreach vd [array names vseedcount $view,*] {
607 unset vseedcount($vd)
609 catch {unset ordertok}
612 # returns a list of the commits with no children
614 global vdownptr vleftptr varcstart
617 set a [lindex $vdownptr($v) 0]
619 lappend ret [lindex $varcstart($v) $a]
620 set a [lindex $vleftptr($v) $a]
625 proc newvarc {view id} {
626 global varcid varctok parents children vdatemode
627 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
628 global commitdata commitinfo vseedcount varccommits vlastins
630 set a [llength $varctok($view)]
632 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
633 if {![info exists commitinfo($id)]} {
634 parsecommit $id $commitdata($id) 1
636 set cdate [lindex $commitinfo($id) 4]
637 if {![string is integer -strict $cdate]} {
640 if {![info exists vseedcount($view,$cdate)]} {
641 set vseedcount($view,$cdate) -1
643 set c [incr vseedcount($view,$cdate)]
644 set cdate [expr {$cdate ^ 0xffffffff}]
645 set tok "s[strrep $cdate][strrep $c]"
650 if {[llength $children($vid)] > 0} {
651 set kid [lindex $children($vid) end]
652 set k $varcid($view,$kid)
653 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
656 set tok [lindex $varctok($view) $k]
660 set i [lsearch -exact $parents($view,$ki) $id]
661 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
662 append tok [strrep $j]
664 set c [lindex $vlastins($view) $ka]
665 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
667 set b [lindex $vdownptr($view) $ka]
669 set b [lindex $vleftptr($view) $c]
671 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
673 set b [lindex $vleftptr($view) $c]
676 lset vdownptr($view) $ka $a
677 lappend vbackptr($view) 0
679 lset vleftptr($view) $c $a
680 lappend vbackptr($view) $c
682 lset vlastins($view) $ka $a
683 lappend vupptr($view) $ka
684 lappend vleftptr($view) $b
686 lset vbackptr($view) $b $a
688 lappend varctok($view) $tok
689 lappend varcstart($view) $id
690 lappend vdownptr($view) 0
691 lappend varcrow($view) {}
692 lappend varcix($view) {}
693 set varccommits($view,$a) {}
694 lappend vlastins($view) 0
698 proc splitvarc {p v} {
699 global varcid varcstart varccommits varctok
700 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
702 set oa $varcid($v,$p)
703 set ac $varccommits($v,$oa)
704 set i [lsearch -exact $varccommits($v,$oa) $p]
706 set na [llength $varctok($v)]
707 # "%" sorts before "0"...
708 set tok "[lindex $varctok($v) $oa]%[strrep $i]"
709 lappend varctok($v) $tok
710 lappend varcrow($v) {}
711 lappend varcix($v) {}
712 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
713 set varccommits($v,$na) [lrange $ac $i end]
714 lappend varcstart($v) $p
715 foreach id $varccommits($v,$na) {
716 set varcid($v,$id) $na
718 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
719 lappend vlastins($v) [lindex $vlastins($v) $oa]
720 lset vdownptr($v) $oa $na
721 lset vlastins($v) $oa 0
722 lappend vupptr($v) $oa
723 lappend vleftptr($v) 0
724 lappend vbackptr($v) 0
725 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
726 lset vupptr($v) $b $na
730 proc renumbervarc {a v} {
731 global parents children varctok varcstart varccommits
732 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
734 set t1 [clock clicks -milliseconds]
740 if {[info exists isrelated($a)]} {
742 set id [lindex $varccommits($v,$a) end]
743 foreach p $parents($v,$id) {
744 if {[info exists varcid($v,$p)]} {
745 set isrelated($varcid($v,$p)) 1
750 set b [lindex $vdownptr($v) $a]
753 set b [lindex $vleftptr($v) $a]
755 set a [lindex $vupptr($v) $a]
761 if {![info exists kidchanged($a)]} continue
762 set id [lindex $varcstart($v) $a]
763 if {[llength $children($v,$id)] > 1} {
764 set children($v,$id) [lsort -command [list vtokcmp $v] \
767 set oldtok [lindex $varctok($v) $a]
768 if {!$vdatemode($v)} {
774 set kid [last_real_child $v,$id]
776 set k $varcid($v,$kid)
777 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
780 set tok [lindex $varctok($v) $k]
784 set i [lsearch -exact $parents($v,$ki) $id]
785 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
786 append tok [strrep $j]
788 if {$tok eq $oldtok} {
791 set id [lindex $varccommits($v,$a) end]
792 foreach p $parents($v,$id) {
793 if {[info exists varcid($v,$p)]} {
794 set kidchanged($varcid($v,$p)) 1
799 lset varctok($v) $a $tok
800 set b [lindex $vupptr($v) $a]
802 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
805 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
808 set c [lindex $vbackptr($v) $a]
809 set d [lindex $vleftptr($v) $a]
811 lset vdownptr($v) $b $d
813 lset vleftptr($v) $c $d
816 lset vbackptr($v) $d $c
818 if {[lindex $vlastins($v) $b] == $a} {
819 lset vlastins($v) $b $c
821 lset vupptr($v) $a $ka
822 set c [lindex $vlastins($v) $ka]
824 [string compare $tok [lindex $varctok($v) $c]] < 0} {
826 set b [lindex $vdownptr($v) $ka]
828 set b [lindex $vleftptr($v) $c]
831 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
833 set b [lindex $vleftptr($v) $c]
836 lset vdownptr($v) $ka $a
837 lset vbackptr($v) $a 0
839 lset vleftptr($v) $c $a
840 lset vbackptr($v) $a $c
842 lset vleftptr($v) $a $b
844 lset vbackptr($v) $b $a
846 lset vlastins($v) $ka $a
849 foreach id [array names sortkids] {
850 if {[llength $children($v,$id)] > 1} {
851 set children($v,$id) [lsort -command [list vtokcmp $v] \
855 set t2 [clock clicks -milliseconds]
856 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
859 # Fix up the graph after we have found out that in view $v,
860 # $p (a commit that we have already seen) is actually the parent
861 # of the last commit in arc $a.
862 proc fix_reversal {p a v} {
863 global varcid varcstart varctok vupptr
865 set pa $varcid($v,$p)
866 if {$p ne [lindex $varcstart($v) $pa]} {
868 set pa $varcid($v,$p)
870 # seeds always need to be renumbered
871 if {[lindex $vupptr($v) $pa] == 0 ||
872 [string compare [lindex $varctok($v) $a] \
873 [lindex $varctok($v) $pa]] > 0} {
878 proc insertrow {id p v} {
879 global cmitlisted children parents varcid varctok vtokmod
880 global varccommits ordertok commitidx numcommits curview
881 global targetid targetrow
885 set cmitlisted($vid) 1
886 set children($vid) {}
887 set parents($vid) [list $p]
888 set a [newvarc $v $id]
890 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
893 lappend varccommits($v,$a) $id
895 if {[llength [lappend children($vp) $id]] > 1} {
896 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
897 catch {unset ordertok}
899 fix_reversal $p $a $v
901 if {$v == $curview} {
902 set numcommits $commitidx($v)
904 if {[info exists targetid]} {
905 if {![comes_before $targetid $p]} {
912 proc insertfakerow {id p} {
913 global varcid varccommits parents children cmitlisted
914 global commitidx varctok vtokmod targetid targetrow curview numcommits
918 set i [lsearch -exact $varccommits($v,$a) $p]
920 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
923 set children($v,$id) {}
924 set parents($v,$id) [list $p]
925 set varcid($v,$id) $a
926 lappend children($v,$p) $id
927 set cmitlisted($v,$id) 1
928 set numcommits [incr commitidx($v)]
929 # note we deliberately don't update varcstart($v) even if $i == 0
930 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
932 if {[info exists targetid]} {
933 if {![comes_before $targetid $p]} {
941 proc removefakerow {id} {
942 global varcid varccommits parents children commitidx
943 global varctok vtokmod cmitlisted currentid selectedline
944 global targetid curview numcommits
947 if {[llength $parents($v,$id)] != 1} {
948 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
951 set p [lindex $parents($v,$id) 0]
952 set a $varcid($v,$id)
953 set i [lsearch -exact $varccommits($v,$a) $id]
955 puts "oops: removefakerow can't find [shortids $id] on arc $a"
959 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
960 unset parents($v,$id)
961 unset children($v,$id)
962 unset cmitlisted($v,$id)
963 set numcommits [incr commitidx($v) -1]
964 set j [lsearch -exact $children($v,$p) $id]
966 set children($v,$p) [lreplace $children($v,$p) $j $j]
969 if {[info exist currentid] && $id eq $currentid} {
973 if {[info exists targetid] && $targetid eq $id} {
980 proc first_real_child {vp} {
981 global children nullid nullid2
983 foreach id $children($vp) {
984 if {$id ne $nullid && $id ne $nullid2} {
991 proc last_real_child {vp} {
992 global children nullid nullid2
994 set kids $children($vp)
995 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
996 set id [lindex $kids $i]
997 if {$id ne $nullid && $id ne $nullid2} {
1004 proc vtokcmp {v a b} {
1005 global varctok varcid
1007 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1008 [lindex $varctok($v) $varcid($v,$b)]]
1011 # This assumes that if lim is not given, the caller has checked that
1012 # arc a's token is less than $vtokmod($v)
1013 proc modify_arc {v a {lim {}}} {
1014 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1017 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1020 set r [lindex $varcrow($v) $a]
1021 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1024 set vtokmod($v) [lindex $varctok($v) $a]
1026 if {$v == $curview} {
1027 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1028 set a [lindex $vupptr($v) $a]
1034 set lim [llength $varccommits($v,$a)]
1036 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1043 proc update_arcrows {v} {
1044 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1045 global varcid vrownum varcorder varcix varccommits
1046 global vupptr vdownptr vleftptr varctok
1047 global displayorder parentlist curview cached_commitrow
1049 if {$vrowmod($v) == $commitidx($v)} return
1050 if {$v == $curview} {
1051 if {[llength $displayorder] > $vrowmod($v)} {
1052 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1053 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1055 catch {unset cached_commitrow}
1057 set narctot [expr {[llength $varctok($v)] - 1}]
1059 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1060 # go up the tree until we find something that has a row number,
1061 # or we get to a seed
1062 set a [lindex $vupptr($v) $a]
1065 set a [lindex $vdownptr($v) 0]
1068 set varcorder($v) [list $a]
1069 lset varcix($v) $a 0
1070 lset varcrow($v) $a 0
1074 set arcn [lindex $varcix($v) $a]
1075 if {[llength $vrownum($v)] > $arcn + 1} {
1076 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1077 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1079 set row [lindex $varcrow($v) $a]
1083 incr row [llength $varccommits($v,$a)]
1084 # go down if possible
1085 set b [lindex $vdownptr($v) $a]
1087 # if not, go left, or go up until we can go left
1089 set b [lindex $vleftptr($v) $a]
1091 set a [lindex $vupptr($v) $a]
1097 lappend vrownum($v) $row
1098 lappend varcorder($v) $a
1099 lset varcix($v) $a $arcn
1100 lset varcrow($v) $a $row
1102 set vtokmod($v) [lindex $varctok($v) $p]
1104 set vrowmod($v) $row
1105 if {[info exists currentid]} {
1106 set selectedline [rowofcommit $currentid]
1110 # Test whether view $v contains commit $id
1111 proc commitinview {id v} {
1114 return [info exists varcid($v,$id)]
1117 # Return the row number for commit $id in the current view
1118 proc rowofcommit {id} {
1119 global varcid varccommits varcrow curview cached_commitrow
1120 global varctok vtokmod
1123 if {![info exists varcid($v,$id)]} {
1124 puts "oops rowofcommit no arc for [shortids $id]"
1127 set a $varcid($v,$id)
1128 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1131 if {[info exists cached_commitrow($id)]} {
1132 return $cached_commitrow($id)
1134 set i [lsearch -exact $varccommits($v,$a) $id]
1136 puts "oops didn't find commit [shortids $id] in arc $a"
1139 incr i [lindex $varcrow($v) $a]
1140 set cached_commitrow($id) $i
1144 # Returns 1 if a is on an earlier row than b, otherwise 0
1145 proc comes_before {a b} {
1146 global varcid varctok curview
1149 if {$a eq $b || ![info exists varcid($v,$a)] || \
1150 ![info exists varcid($v,$b)]} {
1153 if {$varcid($v,$a) != $varcid($v,$b)} {
1154 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1155 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1157 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1160 proc bsearch {l elt} {
1161 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1166 while {$hi - $lo > 1} {
1167 set mid [expr {int(($lo + $hi) / 2)}]
1168 set t [lindex $l $mid]
1171 } elseif {$elt > $t} {
1180 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1181 proc make_disporder {start end} {
1182 global vrownum curview commitidx displayorder parentlist
1183 global varccommits varcorder parents vrowmod varcrow
1184 global d_valid_start d_valid_end
1186 if {$end > $vrowmod($curview)} {
1187 update_arcrows $curview
1189 set ai [bsearch $vrownum($curview) $start]
1190 set start [lindex $vrownum($curview) $ai]
1191 set narc [llength $vrownum($curview)]
1192 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1193 set a [lindex $varcorder($curview) $ai]
1194 set l [llength $displayorder]
1195 set al [llength $varccommits($curview,$a)]
1196 if {$l < $r + $al} {
1198 set pad [ntimes [expr {$r - $l}] {}]
1199 set displayorder [concat $displayorder $pad]
1200 set parentlist [concat $parentlist $pad]
1201 } elseif {$l > $r} {
1202 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1203 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1205 foreach id $varccommits($curview,$a) {
1206 lappend displayorder $id
1207 lappend parentlist $parents($curview,$id)
1209 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1211 foreach id $varccommits($curview,$a) {
1212 lset displayorder $i $id
1213 lset parentlist $i $parents($curview,$id)
1221 proc commitonrow {row} {
1224 set id [lindex $displayorder $row]
1226 make_disporder $row [expr {$row + 1}]
1227 set id [lindex $displayorder $row]
1232 proc closevarcs {v} {
1233 global varctok varccommits varcid parents children
1234 global cmitlisted commitidx vtokmod
1236 set missing_parents 0
1238 set narcs [llength $varctok($v)]
1239 for {set a 1} {$a < $narcs} {incr a} {
1240 set id [lindex $varccommits($v,$a) end]
1241 foreach p $parents($v,$id) {
1242 if {[info exists varcid($v,$p)]} continue
1243 # add p as a new commit
1244 incr missing_parents
1245 set cmitlisted($v,$p) 0
1246 set parents($v,$p) {}
1247 if {[llength $children($v,$p)] == 1 &&
1248 [llength $parents($v,$id)] == 1} {
1251 set b [newvarc $v $p]
1253 set varcid($v,$p) $b
1254 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1257 lappend varccommits($v,$b) $p
1259 set scripts [check_interest $p $scripts]
1262 if {$missing_parents > 0} {
1263 foreach s $scripts {
1269 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1270 # Assumes we already have an arc for $rwid.
1271 proc rewrite_commit {v id rwid} {
1272 global children parents varcid varctok vtokmod varccommits
1274 foreach ch $children($v,$id) {
1275 # make $rwid be $ch's parent in place of $id
1276 set i [lsearch -exact $parents($v,$ch) $id]
1278 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1280 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1281 # add $ch to $rwid's children and sort the list if necessary
1282 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1283 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1284 $children($v,$rwid)]
1286 # fix the graph after joining $id to $rwid
1287 set a $varcid($v,$ch)
1288 fix_reversal $rwid $a $v
1289 # parentlist is wrong for the last element of arc $a
1290 # even if displayorder is right, hence the 3rd arg here
1291 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1295 # Mechanism for registering a command to be executed when we come
1296 # across a particular commit. To handle the case when only the
1297 # prefix of the commit is known, the commitinterest array is now
1298 # indexed by the first 4 characters of the ID. Each element is a
1299 # list of id, cmd pairs.
1300 proc interestedin {id cmd} {
1301 global commitinterest
1303 lappend commitinterest([string range $id 0 3]) $id $cmd
1306 proc check_interest {id scripts} {
1307 global commitinterest
1309 set prefix [string range $id 0 3]
1310 if {[info exists commitinterest($prefix)]} {
1312 foreach {i script} $commitinterest($prefix) {
1313 if {[string match "$i*" $id]} {
1314 lappend scripts [string map [list "%I" $id "%P" $i] $script]
1316 lappend newlist $i $script
1319 if {$newlist ne {}} {
1320 set commitinterest($prefix) $newlist
1322 unset commitinterest($prefix)
1328 proc getcommitlines {fd inst view updating} {
1329 global cmitlisted leftover
1330 global commitidx commitdata vdatemode
1331 global parents children curview hlview
1332 global idpending ordertok
1333 global varccommits varcid varctok vtokmod vfilelimit
1335 set stuff [read $fd 500000]
1336 # git log doesn't terminate the last commit with a null...
1337 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1344 global commfd viewcomplete viewactive viewname
1345 global viewinstances
1347 set i [lsearch -exact $viewinstances($view) $inst]
1349 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1351 # set it blocking so we wait for the process to terminate
1352 fconfigure $fd -blocking 1
1353 if {[catch {close $fd} err]} {
1355 if {$view != $curview} {
1356 set fv " for the \"$viewname($view)\" view"
1358 if {[string range $err 0 4] == "usage"} {
1359 set err "Gitk: error reading commits$fv:\
1360 bad arguments to git log."
1361 if {$viewname($view) eq "Command line"} {
1363 " (Note: arguments to gitk are passed to git log\
1364 to allow selection of commits to be displayed.)"
1367 set err "Error reading commits$fv: $err"
1371 if {[incr viewactive($view) -1] <= 0} {
1372 set viewcomplete($view) 1
1373 # Check if we have seen any ids listed as parents that haven't
1374 # appeared in the list
1378 if {$view == $curview} {
1387 set i [string first "\0" $stuff $start]
1389 append leftover($inst) [string range $stuff $start end]
1393 set cmit $leftover($inst)
1394 append cmit [string range $stuff 0 [expr {$i - 1}]]
1395 set leftover($inst) {}
1397 set cmit [string range $stuff $start [expr {$i - 1}]]
1399 set start [expr {$i + 1}]
1400 set j [string first "\n" $cmit]
1403 if {$j >= 0 && [string match "commit *" $cmit]} {
1404 set ids [string range $cmit 7 [expr {$j - 1}]]
1405 if {[string match {[-^<>]*} $ids]} {
1406 switch -- [string index $ids 0] {
1412 set ids [string range $ids 1 end]
1416 if {[string length $id] != 40} {
1424 if {[string length $shortcmit] > 80} {
1425 set shortcmit "[string range $shortcmit 0 80]..."
1427 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1430 set id [lindex $ids 0]
1433 if {!$listed && $updating && ![info exists varcid($vid)] &&
1434 $vfilelimit($view) ne {}} {
1435 # git log doesn't rewrite parents for unlisted commits
1436 # when doing path limiting, so work around that here
1437 # by working out the rewritten parent with git rev-list
1438 # and if we already know about it, using the rewritten
1439 # parent as a substitute parent for $id's children.
1441 set rwid [exec git rev-list --first-parent --max-count=1 \
1442 $id -- $vfilelimit($view)]
1444 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1445 # use $rwid in place of $id
1446 rewrite_commit $view $id $rwid
1453 if {[info exists varcid($vid)]} {
1454 if {$cmitlisted($vid) || !$listed} continue
1458 set olds [lrange $ids 1 end]
1462 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1463 set cmitlisted($vid) $listed
1464 set parents($vid) $olds
1465 if {![info exists children($vid)]} {
1466 set children($vid) {}
1467 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1468 set k [lindex $children($vid) 0]
1469 if {[llength $parents($view,$k)] == 1 &&
1470 (!$vdatemode($view) ||
1471 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1472 set a $varcid($view,$k)
1477 set a [newvarc $view $id]
1479 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1482 if {![info exists varcid($vid)]} {
1484 lappend varccommits($view,$a) $id
1485 incr commitidx($view)
1490 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1492 if {[llength [lappend children($vp) $id]] > 1 &&
1493 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1494 set children($vp) [lsort -command [list vtokcmp $view] \
1496 catch {unset ordertok}
1498 if {[info exists varcid($view,$p)]} {
1499 fix_reversal $p $a $view
1505 set scripts [check_interest $id $scripts]
1509 global numcommits hlview
1511 if {$view == $curview} {
1512 set numcommits $commitidx($view)
1515 if {[info exists hlview] && $view == $hlview} {
1516 # we never actually get here...
1519 foreach s $scripts {
1526 proc chewcommits {} {
1527 global curview hlview viewcomplete
1528 global pending_select
1531 if {$viewcomplete($curview)} {
1532 global commitidx varctok
1533 global numcommits startmsecs
1535 if {[info exists pending_select]} {
1537 reset_pending_select {}
1539 if {[commitinview $pending_select $curview]} {
1540 selectline [rowofcommit $pending_select] 1
1542 set row [first_real_row]
1546 if {$commitidx($curview) > 0} {
1547 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1548 #puts "overall $ms ms for $numcommits commits"
1549 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1551 show_status [mc "No commits selected"]
1558 proc readcommit {id} {
1559 if {[catch {set contents [exec git cat-file commit $id]}]} return
1560 parsecommit $id $contents 0
1563 proc parsecommit {id contents listed} {
1564 global commitinfo cdate
1573 set hdrend [string first "\n\n" $contents]
1575 # should never happen...
1576 set hdrend [string length $contents]
1578 set header [string range $contents 0 [expr {$hdrend - 1}]]
1579 set comment [string range $contents [expr {$hdrend + 2}] end]
1580 foreach line [split $header "\n"] {
1581 set tag [lindex $line 0]
1582 if {$tag == "author"} {
1583 set audate [lindex $line end-1]
1584 set auname [lrange $line 1 end-2]
1585 } elseif {$tag == "committer"} {
1586 set comdate [lindex $line end-1]
1587 set comname [lrange $line 1 end-2]
1591 # take the first non-blank line of the comment as the headline
1592 set headline [string trimleft $comment]
1593 set i [string first "\n" $headline]
1595 set headline [string range $headline 0 $i]
1597 set headline [string trimright $headline]
1598 set i [string first "\r" $headline]
1600 set headline [string trimright [string range $headline 0 $i]]
1603 # git log indents the comment by 4 spaces;
1604 # if we got this via git cat-file, add the indentation
1606 foreach line [split $comment "\n"] {
1607 append newcomment " "
1608 append newcomment $line
1609 append newcomment "\n"
1611 set comment $newcomment
1613 if {$comdate != {}} {
1614 set cdate($id) $comdate
1616 set commitinfo($id) [list $headline $auname $audate \
1617 $comname $comdate $comment]
1620 proc getcommit {id} {
1621 global commitdata commitinfo
1623 if {[info exists commitdata($id)]} {
1624 parsecommit $id $commitdata($id) 1
1627 if {![info exists commitinfo($id)]} {
1628 set commitinfo($id) [list [mc "No commit information available"]]
1634 # Expand an abbreviated commit ID to a list of full 40-char IDs that match
1635 # and are present in the current view.
1636 # This is fairly slow...
1637 proc longid {prefix} {
1638 global varcid curview
1641 foreach match [array names varcid "$curview,$prefix*"] {
1642 lappend ids [lindex [split $match ","] 1]
1648 global tagids idtags headids idheads tagobjid
1649 global otherrefids idotherrefs mainhead mainheadid
1650 global selecthead selectheadid
1652 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1655 set refd [open [list | git show-ref -d] r]
1656 while {[gets $refd line] >= 0} {
1657 if {[string index $line 40] ne " "} continue
1658 set id [string range $line 0 39]
1659 set ref [string range $line 41 end]
1660 if {![string match "refs/*" $ref]} continue
1661 set name [string range $ref 5 end]
1662 if {[string match "remotes/*" $name]} {
1663 if {![string match "*/HEAD" $name]} {
1664 set headids($name) $id
1665 lappend idheads($id) $name
1667 } elseif {[string match "heads/*" $name]} {
1668 set name [string range $name 6 end]
1669 set headids($name) $id
1670 lappend idheads($id) $name
1671 } elseif {[string match "tags/*" $name]} {
1672 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1673 # which is what we want since the former is the commit ID
1674 set name [string range $name 5 end]
1675 if {[string match "*^{}" $name]} {
1676 set name [string range $name 0 end-3]
1678 set tagobjid($name) $id
1680 set tagids($name) $id
1681 lappend idtags($id) $name
1683 set otherrefids($name) $id
1684 lappend idotherrefs($id) $name
1691 set mainheadid [exec git rev-parse HEAD]
1692 set thehead [exec git symbolic-ref HEAD]
1693 if {[string match "refs/heads/*" $thehead]} {
1694 set mainhead [string range $thehead 11 end]
1698 if {$selecthead ne {}} {
1700 set selectheadid [exec git rev-parse --verify $selecthead]
1705 # skip over fake commits
1706 proc first_real_row {} {
1707 global nullid nullid2 numcommits
1709 for {set row 0} {$row < $numcommits} {incr row} {
1710 set id [commitonrow $row]
1711 if {$id ne $nullid && $id ne $nullid2} {
1718 # update things for a head moved to a child of its previous location
1719 proc movehead {id name} {
1720 global headids idheads
1722 removehead $headids($name) $name
1723 set headids($name) $id
1724 lappend idheads($id) $name
1727 # update things when a head has been removed
1728 proc removehead {id name} {
1729 global headids idheads
1731 if {$idheads($id) eq $name} {
1734 set i [lsearch -exact $idheads($id) $name]
1736 set idheads($id) [lreplace $idheads($id) $i $i]
1739 unset headids($name)
1742 proc show_error {w top msg} {
1743 message $w.m -text $msg -justify center -aspect 400
1744 pack $w.m -side top -fill x -padx 20 -pady 20
1745 button $w.ok -text [mc OK] -command "destroy $top"
1746 pack $w.ok -side bottom -fill x
1747 bind $top <Visibility> "grab $top; focus $top"
1748 bind $top <Key-Return> "destroy $top"
1749 bind $top <Key-space> "destroy $top"
1750 bind $top <Key-Escape> "destroy $top"
1754 proc error_popup {msg {owner .}} {
1757 wm transient $w $owner
1758 show_error $w $w $msg
1761 proc confirm_popup {msg {owner .}} {
1766 wm transient $w $owner
1767 message $w.m -text $msg -justify center -aspect 400
1768 pack $w.m -side top -fill x -padx 20 -pady 20
1769 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1770 pack $w.ok -side left -fill x
1771 button $w.cancel -text [mc Cancel] -command "destroy $w"
1772 pack $w.cancel -side right -fill x
1773 bind $w <Visibility> "grab $w; focus $w"
1774 bind $w <Key-Return> "set confirm_ok 1; destroy $w"
1775 bind $w <Key-space> "set confirm_ok 1; destroy $w"
1776 bind $w <Key-Escape> "destroy $w"
1781 proc setoptions {} {
1782 option add *Panedwindow.showHandle 1 startupFile
1783 option add *Panedwindow.sashRelief raised startupFile
1784 option add *Button.font uifont startupFile
1785 option add *Checkbutton.font uifont startupFile
1786 option add *Radiobutton.font uifont startupFile
1787 option add *Menu.font uifont startupFile
1788 option add *Menubutton.font uifont startupFile
1789 option add *Label.font uifont startupFile
1790 option add *Message.font uifont startupFile
1791 option add *Entry.font uifont startupFile
1794 # Make a menu and submenus.
1795 # m is the window name for the menu, items is the list of menu items to add.
1796 # Each item is a list {mc label type description options...}
1797 # mc is ignored; it's so we can put mc there to alert xgettext
1798 # label is the string that appears in the menu
1799 # type is cascade, command or radiobutton (should add checkbutton)
1800 # description depends on type; it's the sublist for cascade, the
1801 # command to invoke for command, or {variable value} for radiobutton
1802 proc makemenu {m items} {
1804 if {[tk windowingsystem] eq {aqua}} {
1810 set name [mc [lindex $i 1]]
1811 set type [lindex $i 2]
1812 set thing [lindex $i 3]
1813 set params [list $type]
1815 set u [string first "&" [string map {&& x} $name]]
1816 lappend params -label [string map {&& & & {}} $name]
1818 lappend params -underline $u
1823 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1824 lappend params -menu $m.$submenu
1827 lappend params -command $thing
1830 lappend params -variable [lindex $thing 0] \
1831 -value [lindex $thing 1]
1834 set tail [lrange $i 4 end]
1835 regsub -all {\yMeta1\y} $tail $Meta1 tail
1836 eval $m add $params $tail
1837 if {$type eq "cascade"} {
1838 makemenu $m.$submenu $thing
1843 # translate string and remove ampersands
1845 return [string map {&& & & {}} [mc $str]]
1848 proc makewindow {} {
1849 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1851 global findtype findtypemenu findloc findstring fstring geometry
1852 global entries sha1entry sha1string sha1but
1853 global diffcontextstring diffcontext
1855 global maincursor textcursor curtextcursor
1856 global rowctxmenu fakerowmenu mergemax wrapcomment
1857 global highlight_files gdttype
1858 global searchstring sstring
1859 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1860 global headctxmenu progresscanv progressitem progresscoords statusw
1861 global fprogitem fprogcoord lastprogupdate progupdatepending
1862 global rprogitem rprogcoord rownumsel numcommits
1865 # The "mc" arguments here are purely so that xgettext
1866 # sees the following string as needing to be translated
1868 {mc "File" cascade {
1869 {mc "Update" command updatecommits -accelerator F5}
1870 {mc "Reload" command reloadcommits -accelerator Meta1-F5}
1871 {mc "Reread references" command rereadrefs}
1872 {mc "List references" command showrefs -accelerator F2}
1873 {mc "Quit" command doquit -accelerator Meta1-Q}
1875 {mc "Edit" cascade {
1876 {mc "Preferences" command doprefs}
1878 {mc "View" cascade {
1879 {mc "New view..." command {newview 0} -accelerator Shift-F4}
1880 {mc "Edit view..." command editview -state disabled -accelerator F4}
1881 {mc "Delete view" command delview -state disabled}
1883 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
1885 {mc "Help" cascade {
1886 {mc "About gitk" command about}
1887 {mc "Key bindings" command keys}
1890 . configure -menu .bar
1892 # the gui has upper and lower half, parts of a paned window.
1893 panedwindow .ctop -orient vertical
1895 # possibly use assumed geometry
1896 if {![info exists geometry(pwsash0)]} {
1897 set geometry(topheight) [expr {15 * $linespc}]
1898 set geometry(topwidth) [expr {80 * $charspc}]
1899 set geometry(botheight) [expr {15 * $linespc}]
1900 set geometry(botwidth) [expr {50 * $charspc}]
1901 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1902 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1905 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1906 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1908 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1910 # create three canvases
1911 set cscroll .tf.histframe.csb
1912 set canv .tf.histframe.pwclist.canv
1914 -selectbackground $selectbgcolor \
1915 -background $bgcolor -bd 0 \
1916 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1917 .tf.histframe.pwclist add $canv
1918 set canv2 .tf.histframe.pwclist.canv2
1920 -selectbackground $selectbgcolor \
1921 -background $bgcolor -bd 0 -yscrollincr $linespc
1922 .tf.histframe.pwclist add $canv2
1923 set canv3 .tf.histframe.pwclist.canv3
1925 -selectbackground $selectbgcolor \
1926 -background $bgcolor -bd 0 -yscrollincr $linespc
1927 .tf.histframe.pwclist add $canv3
1928 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1929 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1931 # a scroll bar to rule them
1932 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1933 pack $cscroll -side right -fill y
1934 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1935 lappend bglist $canv $canv2 $canv3
1936 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1938 # we have two button bars at bottom of top frame. Bar 1
1940 frame .tf.lbar -height 15
1942 set sha1entry .tf.bar.sha1
1943 set entries $sha1entry
1944 set sha1but .tf.bar.sha1label
1945 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1946 -command gotocommit -width 8
1947 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1948 pack .tf.bar.sha1label -side left
1949 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1950 trace add variable sha1string write sha1change
1951 pack $sha1entry -side left -pady 2
1953 image create bitmap bm-left -data {
1954 #define left_width 16
1955 #define left_height 16
1956 static unsigned char left_bits[] = {
1957 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1958 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1959 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1961 image create bitmap bm-right -data {
1962 #define right_width 16
1963 #define right_height 16
1964 static unsigned char right_bits[] = {
1965 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1966 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1967 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1969 button .tf.bar.leftbut -image bm-left -command goback \
1970 -state disabled -width 26
1971 pack .tf.bar.leftbut -side left -fill y
1972 button .tf.bar.rightbut -image bm-right -command goforw \
1973 -state disabled -width 26
1974 pack .tf.bar.rightbut -side left -fill y
1976 label .tf.bar.rowlabel -text [mc "Row"]
1978 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
1979 -relief sunken -anchor e
1980 label .tf.bar.rowlabel2 -text "/"
1981 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
1982 -relief sunken -anchor e
1983 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
1986 trace add variable selectedline write selectedline_change
1988 # Status label and progress bar
1989 set statusw .tf.bar.status
1990 label $statusw -width 15 -relief sunken
1991 pack $statusw -side left -padx 5
1992 set h [expr {[font metrics uifont -linespace] + 2}]
1993 set progresscanv .tf.bar.progress
1994 canvas $progresscanv -relief sunken -height $h -borderwidth 2
1995 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1996 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1997 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1998 pack $progresscanv -side right -expand 1 -fill x
1999 set progresscoords {0 0}
2002 bind $progresscanv <Configure> adjustprogress
2003 set lastprogupdate [clock clicks -milliseconds]
2004 set progupdatepending 0
2006 # build up the bottom bar of upper window
2007 label .tf.lbar.flabel -text "[mc "Find"] "
2008 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
2009 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
2010 label .tf.lbar.flab2 -text " [mc "commit"] "
2011 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2013 set gdttype [mc "containing:"]
2014 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
2015 [mc "containing:"] \
2016 [mc "touching paths:"] \
2017 [mc "adding/removing string:"]]
2018 trace add variable gdttype write gdttype_change
2019 pack .tf.lbar.gdttype -side left -fill y
2022 set fstring .tf.lbar.findstring
2023 lappend entries $fstring
2024 entry $fstring -width 30 -font textfont -textvariable findstring
2025 trace add variable findstring write find_change
2026 set findtype [mc "Exact"]
2027 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
2028 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2029 trace add variable findtype write findcom_change
2030 set findloc [mc "All fields"]
2031 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2032 [mc "Comments"] [mc "Author"] [mc "Committer"]
2033 trace add variable findloc write find_change
2034 pack .tf.lbar.findloc -side right
2035 pack .tf.lbar.findtype -side right
2036 pack $fstring -side left -expand 1 -fill x
2038 # Finish putting the upper half of the viewer together
2039 pack .tf.lbar -in .tf -side bottom -fill x
2040 pack .tf.bar -in .tf -side bottom -fill x
2041 pack .tf.histframe -fill both -side top -expand 1
2043 .ctop paneconfigure .tf -height $geometry(topheight)
2044 .ctop paneconfigure .tf -width $geometry(topwidth)
2046 # now build up the bottom
2047 panedwindow .pwbottom -orient horizontal
2049 # lower left, a text box over search bar, scroll bar to the right
2050 # if we know window height, then that will set the lower text height, otherwise
2051 # we set lower text height which will drive window height
2052 if {[info exists geometry(main)]} {
2053 frame .bleft -width $geometry(botwidth)
2055 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2061 button .bleft.top.search -text [mc "Search"] -command dosearch
2062 pack .bleft.top.search -side left -padx 5
2063 set sstring .bleft.top.sstring
2064 entry $sstring -width 20 -font textfont -textvariable searchstring
2065 lappend entries $sstring
2066 trace add variable searchstring write incrsearch
2067 pack $sstring -side left -expand 1 -fill x
2068 radiobutton .bleft.mid.diff -text [mc "Diff"] \
2069 -command changediffdisp -variable diffelide -value {0 0}
2070 radiobutton .bleft.mid.old -text [mc "Old version"] \
2071 -command changediffdisp -variable diffelide -value {0 1}
2072 radiobutton .bleft.mid.new -text [mc "New version"] \
2073 -command changediffdisp -variable diffelide -value {1 0}
2074 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2075 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2076 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
2077 -from 1 -increment 1 -to 10000000 \
2078 -validate all -validatecommand "diffcontextvalidate %P" \
2079 -textvariable diffcontextstring
2080 .bleft.mid.diffcontext set $diffcontext
2081 trace add variable diffcontextstring write diffcontextchange
2082 lappend entries .bleft.mid.diffcontext
2083 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2084 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2085 -command changeignorespace -variable ignorespace
2086 pack .bleft.mid.ignspace -side left -padx 5
2087 set ctext .bleft.bottom.ctext
2088 text $ctext -background $bgcolor -foreground $fgcolor \
2089 -state disabled -font textfont \
2090 -yscrollcommand scrolltext -wrap none \
2091 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2093 $ctext conf -tabstyle wordprocessor
2095 scrollbar .bleft.bottom.sb -command "$ctext yview"
2096 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
2098 pack .bleft.top -side top -fill x
2099 pack .bleft.mid -side top -fill x
2100 grid $ctext .bleft.bottom.sb -sticky nsew
2101 grid .bleft.bottom.sbhorizontal -sticky ew
2102 grid columnconfigure .bleft.bottom 0 -weight 1
2103 grid rowconfigure .bleft.bottom 0 -weight 1
2104 grid rowconfigure .bleft.bottom 1 -weight 0
2105 pack .bleft.bottom -side top -fill both -expand 1
2106 lappend bglist $ctext
2107 lappend fglist $ctext
2109 $ctext tag conf comment -wrap $wrapcomment
2110 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2111 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2112 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2113 $ctext tag conf dresult -fore [lindex $diffcolors 1]
2114 $ctext tag conf m0 -fore red
2115 $ctext tag conf m1 -fore blue
2116 $ctext tag conf m2 -fore green
2117 $ctext tag conf m3 -fore purple
2118 $ctext tag conf m4 -fore brown
2119 $ctext tag conf m5 -fore "#009090"
2120 $ctext tag conf m6 -fore magenta
2121 $ctext tag conf m7 -fore "#808000"
2122 $ctext tag conf m8 -fore "#009000"
2123 $ctext tag conf m9 -fore "#ff0080"
2124 $ctext tag conf m10 -fore cyan
2125 $ctext tag conf m11 -fore "#b07070"
2126 $ctext tag conf m12 -fore "#70b0f0"
2127 $ctext tag conf m13 -fore "#70f0b0"
2128 $ctext tag conf m14 -fore "#f0b070"
2129 $ctext tag conf m15 -fore "#ff70b0"
2130 $ctext tag conf mmax -fore darkgrey
2132 $ctext tag conf mresult -font textfontbold
2133 $ctext tag conf msep -font textfontbold
2134 $ctext tag conf found -back yellow
2136 .pwbottom add .bleft
2137 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2142 radiobutton .bright.mode.patch -text [mc "Patch"] \
2143 -command reselectline -variable cmitmode -value "patch"
2144 radiobutton .bright.mode.tree -text [mc "Tree"] \
2145 -command reselectline -variable cmitmode -value "tree"
2146 grid .bright.mode.patch .bright.mode.tree -sticky ew
2147 pack .bright.mode -side top -fill x
2148 set cflist .bright.cfiles
2149 set indent [font measure mainfont "nn"]
2151 -selectbackground $selectbgcolor \
2152 -background $bgcolor -foreground $fgcolor \
2154 -tabs [list $indent [expr {2 * $indent}]] \
2155 -yscrollcommand ".bright.sb set" \
2156 -cursor [. cget -cursor] \
2157 -spacing1 1 -spacing3 1
2158 lappend bglist $cflist
2159 lappend fglist $cflist
2160 scrollbar .bright.sb -command "$cflist yview"
2161 pack .bright.sb -side right -fill y
2162 pack $cflist -side left -fill both -expand 1
2163 $cflist tag configure highlight \
2164 -background [$cflist cget -selectbackground]
2165 $cflist tag configure bold -font mainfontbold
2167 .pwbottom add .bright
2170 # restore window width & height if known
2171 if {[info exists geometry(main)]} {
2172 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2173 if {$w > [winfo screenwidth .]} {
2174 set w [winfo screenwidth .]
2176 if {$h > [winfo screenheight .]} {
2177 set h [winfo screenheight .]
2179 wm geometry . "${w}x$h"
2183 if {[tk windowingsystem] eq {aqua}} {
2189 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2190 pack .ctop -fill both -expand 1
2191 bindall <1> {selcanvline %W %x %y}
2192 #bindall <B1-Motion> {selcanvline %W %x %y}
2193 if {[tk windowingsystem] == "win32"} {
2194 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2195 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2197 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2198 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2199 if {[tk windowingsystem] eq "aqua"} {
2200 bindall <MouseWheel> {
2201 set delta [expr {- (%D)}]
2202 allcanvs yview scroll $delta units
2206 bindall <2> "canvscan mark %W %x %y"
2207 bindall <B2-Motion> "canvscan dragto %W %x %y"
2208 bindkey <Home> selfirstline
2209 bindkey <End> sellastline
2210 bind . <Key-Up> "selnextline -1"
2211 bind . <Key-Down> "selnextline 1"
2212 bind . <Shift-Key-Up> "dofind -1 0"
2213 bind . <Shift-Key-Down> "dofind 1 0"
2214 bindkey <Key-Right> "goforw"
2215 bindkey <Key-Left> "goback"
2216 bind . <Key-Prior> "selnextpage -1"
2217 bind . <Key-Next> "selnextpage 1"
2218 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2219 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2220 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2221 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2222 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2223 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2224 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2225 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2226 bindkey <Key-space> "$ctext yview scroll 1 pages"
2227 bindkey p "selnextline -1"
2228 bindkey n "selnextline 1"
2231 bindkey i "selnextline -1"
2232 bindkey k "selnextline 1"
2236 bindkey d "$ctext yview scroll 18 units"
2237 bindkey u "$ctext yview scroll -18 units"
2238 bindkey / {dofind 1 1}
2239 bindkey <Key-Return> {dofind 1 1}
2240 bindkey ? {dofind -1 1}
2242 bind . <F5> updatecommits
2243 bind . <$M1B-F5> reloadcommits
2244 bind . <F2> showrefs
2245 bind . <Shift-F4> {newview 0}
2246 catch { bind . <Shift-Key-XF86_Switch_VT_4> {newview 0} }
2247 bind . <F4> edit_or_newview
2248 bind . <$M1B-q> doquit
2249 bind . <$M1B-f> {dofind 1 1}
2250 bind . <$M1B-g> {dofind 1 0}
2251 bind . <$M1B-r> dosearchback
2252 bind . <$M1B-s> dosearch
2253 bind . <$M1B-equal> {incrfont 1}
2254 bind . <$M1B-plus> {incrfont 1}
2255 bind . <$M1B-KP_Add> {incrfont 1}
2256 bind . <$M1B-minus> {incrfont -1}
2257 bind . <$M1B-KP_Subtract> {incrfont -1}
2258 wm protocol . WM_DELETE_WINDOW doquit
2259 bind . <Destroy> {stop_backends}
2260 bind . <Button-1> "click %W"
2261 bind $fstring <Key-Return> {dofind 1 1}
2262 bind $sha1entry <Key-Return> {gotocommit; break}
2263 bind $sha1entry <<PasteSelection>> clearsha1
2264 bind $cflist <1> {sel_flist %W %x %y; break}
2265 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2266 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2268 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2269 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2271 set maincursor [. cget -cursor]
2272 set textcursor [$ctext cget -cursor]
2273 set curtextcursor $textcursor
2275 set rowctxmenu .rowctxmenu
2276 makemenu $rowctxmenu {
2277 {mc "Diff this -> selected" command {diffvssel 0}}
2278 {mc "Diff selected -> this" command {diffvssel 1}}
2279 {mc "Make patch" command mkpatch}
2280 {mc "Create tag" command mktag}
2281 {mc "Write commit to file" command writecommit}
2282 {mc "Create new branch" command mkbranch}
2283 {mc "Cherry-pick this commit" command cherrypick}
2284 {mc "Reset HEAD branch to here" command resethead}
2286 $rowctxmenu configure -tearoff 0
2288 set fakerowmenu .fakerowmenu
2289 makemenu $fakerowmenu {
2290 {mc "Diff this -> selected" command {diffvssel 0}}
2291 {mc "Diff selected -> this" command {diffvssel 1}}
2292 {mc "Make patch" command mkpatch}
2294 $fakerowmenu configure -tearoff 0
2296 set headctxmenu .headctxmenu
2297 makemenu $headctxmenu {
2298 {mc "Check out this branch" command cobranch}
2299 {mc "Remove this branch" command rmbranch}
2301 $headctxmenu configure -tearoff 0
2304 set flist_menu .flistctxmenu
2305 makemenu $flist_menu {
2306 {mc "Highlight this too" command {flist_hl 0}}
2307 {mc "Highlight this only" command {flist_hl 1}}
2308 {mc "External diff" command {external_diff}}
2309 {mc "Blame parent commit" command {external_blame 1}}
2311 $flist_menu configure -tearoff 0
2314 set diff_menu .diffctxmenu
2315 makemenu $diff_menu {
2316 {mc "Show origin of this line" command show_line_source}
2317 {mc "Run git gui blame on this line" command {external_blame_diff}}
2319 $diff_menu configure -tearoff 0
2322 # Windows sends all mouse wheel events to the current focused window, not
2323 # the one where the mouse hovers, so bind those events here and redirect
2324 # to the correct window
2325 proc windows_mousewheel_redirector {W X Y D} {
2326 global canv canv2 canv3
2327 set w [winfo containing -displayof $W $X $Y]
2329 set u [expr {$D < 0 ? 5 : -5}]
2330 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2331 allcanvs yview scroll $u units
2334 $w yview scroll $u units
2340 # Update row number label when selectedline changes
2341 proc selectedline_change {n1 n2 op} {
2342 global selectedline rownumsel
2344 if {$selectedline eq {}} {
2347 set rownumsel [expr {$selectedline + 1}]
2351 # mouse-2 makes all windows scan vertically, but only the one
2352 # the cursor is in scans horizontally
2353 proc canvscan {op w x y} {
2354 global canv canv2 canv3
2355 foreach c [list $canv $canv2 $canv3] {
2364 proc scrollcanv {cscroll f0 f1} {
2365 $cscroll set $f0 $f1
2370 # when we make a key binding for the toplevel, make sure
2371 # it doesn't get triggered when that key is pressed in the
2372 # find string entry widget.
2373 proc bindkey {ev script} {
2376 set escript [bind Entry $ev]
2377 if {$escript == {}} {
2378 set escript [bind Entry <Key>]
2380 foreach e $entries {
2381 bind $e $ev "$escript; break"
2385 # set the focus back to the toplevel for any click outside
2388 global ctext entries
2389 foreach e [concat $entries $ctext] {
2390 if {$w == $e} return
2395 # Adjust the progress bar for a change in requested extent or canvas size
2396 proc adjustprogress {} {
2397 global progresscanv progressitem progresscoords
2398 global fprogitem fprogcoord lastprogupdate progupdatepending
2399 global rprogitem rprogcoord
2401 set w [expr {[winfo width $progresscanv] - 4}]
2402 set x0 [expr {$w * [lindex $progresscoords 0]}]
2403 set x1 [expr {$w * [lindex $progresscoords 1]}]
2404 set h [winfo height $progresscanv]
2405 $progresscanv coords $progressitem $x0 0 $x1 $h
2406 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2407 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2408 set now [clock clicks -milliseconds]
2409 if {$now >= $lastprogupdate + 100} {
2410 set progupdatepending 0
2412 } elseif {!$progupdatepending} {
2413 set progupdatepending 1
2414 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2418 proc doprogupdate {} {
2419 global lastprogupdate progupdatepending
2421 if {$progupdatepending} {
2422 set progupdatepending 0
2423 set lastprogupdate [clock clicks -milliseconds]
2428 proc savestuff {w} {
2429 global canv canv2 canv3 mainfont textfont uifont tabstop
2430 global stuffsaved findmergefiles maxgraphpct
2431 global maxwidth showneartags showlocalchanges
2432 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2433 global cmitmode wrapcomment datetimeformat limitdiffs
2434 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2435 global autoselect extdifftool perfile_attrs markbgcolor
2437 if {$stuffsaved} return
2438 if {![winfo viewable .]} return
2440 set f [open "~/.gitk-new" w]
2441 puts $f [list set mainfont $mainfont]
2442 puts $f [list set textfont $textfont]
2443 puts $f [list set uifont $uifont]
2444 puts $f [list set tabstop $tabstop]
2445 puts $f [list set findmergefiles $findmergefiles]
2446 puts $f [list set maxgraphpct $maxgraphpct]
2447 puts $f [list set maxwidth $maxwidth]
2448 puts $f [list set cmitmode $cmitmode]
2449 puts $f [list set wrapcomment $wrapcomment]
2450 puts $f [list set autoselect $autoselect]
2451 puts $f [list set showneartags $showneartags]
2452 puts $f [list set showlocalchanges $showlocalchanges]
2453 puts $f [list set datetimeformat $datetimeformat]
2454 puts $f [list set limitdiffs $limitdiffs]
2455 puts $f [list set bgcolor $bgcolor]
2456 puts $f [list set fgcolor $fgcolor]
2457 puts $f [list set colors $colors]
2458 puts $f [list set diffcolors $diffcolors]
2459 puts $f [list set markbgcolor $markbgcolor]
2460 puts $f [list set diffcontext $diffcontext]
2461 puts $f [list set selectbgcolor $selectbgcolor]
2462 puts $f [list set extdifftool $extdifftool]
2463 puts $f [list set perfile_attrs $perfile_attrs]
2465 puts $f "set geometry(main) [wm geometry .]"
2466 puts $f "set geometry(topwidth) [winfo width .tf]"
2467 puts $f "set geometry(topheight) [winfo height .tf]"
2468 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2469 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2470 puts $f "set geometry(botwidth) [winfo width .bleft]"
2471 puts $f "set geometry(botheight) [winfo height .bleft]"
2473 puts -nonewline $f "set permviews {"
2474 for {set v 0} {$v < $nextviewnum} {incr v} {
2475 if {$viewperm($v)} {
2476 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2481 file rename -force "~/.gitk-new" "~/.gitk"
2486 proc resizeclistpanes {win w} {
2488 if {[info exists oldwidth($win)]} {
2489 set s0 [$win sash coord 0]
2490 set s1 [$win sash coord 1]
2492 set sash0 [expr {int($w/2 - 2)}]
2493 set sash1 [expr {int($w*5/6 - 2)}]
2495 set factor [expr {1.0 * $w / $oldwidth($win)}]
2496 set sash0 [expr {int($factor * [lindex $s0 0])}]
2497 set sash1 [expr {int($factor * [lindex $s1 0])}]
2501 if {$sash1 < $sash0 + 20} {
2502 set sash1 [expr {$sash0 + 20}]
2504 if {$sash1 > $w - 10} {
2505 set sash1 [expr {$w - 10}]
2506 if {$sash0 > $sash1 - 20} {
2507 set sash0 [expr {$sash1 - 20}]
2511 $win sash place 0 $sash0 [lindex $s0 1]
2512 $win sash place 1 $sash1 [lindex $s1 1]
2514 set oldwidth($win) $w
2517 proc resizecdetpanes {win w} {
2519 if {[info exists oldwidth($win)]} {
2520 set s0 [$win sash coord 0]
2522 set sash0 [expr {int($w*3/4 - 2)}]
2524 set factor [expr {1.0 * $w / $oldwidth($win)}]
2525 set sash0 [expr {int($factor * [lindex $s0 0])}]
2529 if {$sash0 > $w - 15} {
2530 set sash0 [expr {$w - 15}]
2533 $win sash place 0 $sash0 [lindex $s0 1]
2535 set oldwidth($win) $w
2538 proc allcanvs args {
2539 global canv canv2 canv3
2545 proc bindall {event action} {
2546 global canv canv2 canv3
2547 bind $canv $event $action
2548 bind $canv2 $event $action
2549 bind $canv3 $event $action
2555 if {[winfo exists $w]} {
2560 wm title $w [mc "About gitk"]
2562 message $w.m -text [mc "
2563 Gitk - a commit viewer for git
2565 Copyright © 2005-2008 Paul Mackerras
2567 Use and redistribute under the terms of the GNU General Public License"] \
2568 -justify center -aspect 400 -border 2 -bg white -relief groove
2569 pack $w.m -side top -fill x -padx 2 -pady 2
2570 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2571 pack $w.ok -side bottom
2572 bind $w <Visibility> "focus $w.ok"
2573 bind $w <Key-Escape> "destroy $w"
2574 bind $w <Key-Return> "destroy $w"
2579 if {[winfo exists $w]} {
2583 if {[tk windowingsystem] eq {aqua}} {
2589 wm title $w [mc "Gitk key bindings"]
2591 message $w.m -text "
2592 [mc "Gitk key bindings:"]
2594 [mc "<%s-Q> Quit" $M1T]
2595 [mc "<Home> Move to first commit"]
2596 [mc "<End> Move to last commit"]
2597 [mc "<Up>, p, i Move up one commit"]
2598 [mc "<Down>, n, k Move down one commit"]
2599 [mc "<Left>, z, j Go back in history list"]
2600 [mc "<Right>, x, l Go forward in history list"]
2601 [mc "<PageUp> Move up one page in commit list"]
2602 [mc "<PageDown> Move down one page in commit list"]
2603 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2604 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2605 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2606 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2607 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2608 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2609 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2610 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2611 [mc "<Delete>, b Scroll diff view up one page"]
2612 [mc "<Backspace> Scroll diff view up one page"]
2613 [mc "<Space> Scroll diff view down one page"]
2614 [mc "u Scroll diff view up 18 lines"]
2615 [mc "d Scroll diff view down 18 lines"]
2616 [mc "<%s-F> Find" $M1T]
2617 [mc "<%s-G> Move to next find hit" $M1T]
2618 [mc "<Return> Move to next find hit"]
2619 [mc "/ Move to next find hit, or redo find"]
2620 [mc "? Move to previous find hit"]
2621 [mc "f Scroll diff view to next file"]
2622 [mc "<%s-S> Search for next hit in diff view" $M1T]
2623 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2624 [mc "<%s-KP+> Increase font size" $M1T]
2625 [mc "<%s-plus> Increase font size" $M1T]
2626 [mc "<%s-KP-> Decrease font size" $M1T]
2627 [mc "<%s-minus> Decrease font size" $M1T]
2630 -justify left -bg white -border 2 -relief groove
2631 pack $w.m -side top -fill both -padx 2 -pady 2
2632 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2633 bind $w <Key-Escape> [list destroy $w]
2634 pack $w.ok -side bottom
2635 bind $w <Visibility> "focus $w.ok"
2636 bind $w <Key-Escape> "destroy $w"
2637 bind $w <Key-Return> "destroy $w"
2640 # Procedures for manipulating the file list window at the
2641 # bottom right of the overall window.
2643 proc treeview {w l openlevs} {
2644 global treecontents treediropen treeheight treeparent treeindex
2654 set treecontents() {}
2655 $w conf -state normal
2657 while {[string range $f 0 $prefixend] ne $prefix} {
2658 if {$lev <= $openlevs} {
2659 $w mark set e:$treeindex($prefix) "end -1c"
2660 $w mark gravity e:$treeindex($prefix) left
2662 set treeheight($prefix) $ht
2663 incr ht [lindex $htstack end]
2664 set htstack [lreplace $htstack end end]
2665 set prefixend [lindex $prefendstack end]
2666 set prefendstack [lreplace $prefendstack end end]
2667 set prefix [string range $prefix 0 $prefixend]
2670 set tail [string range $f [expr {$prefixend+1}] end]
2671 while {[set slash [string first "/" $tail]] >= 0} {
2674 lappend prefendstack $prefixend
2675 incr prefixend [expr {$slash + 1}]
2676 set d [string range $tail 0 $slash]
2677 lappend treecontents($prefix) $d
2678 set oldprefix $prefix
2680 set treecontents($prefix) {}
2681 set treeindex($prefix) [incr ix]
2682 set treeparent($prefix) $oldprefix
2683 set tail [string range $tail [expr {$slash+1}] end]
2684 if {$lev <= $openlevs} {
2686 set treediropen($prefix) [expr {$lev < $openlevs}]
2687 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2688 $w mark set d:$ix "end -1c"
2689 $w mark gravity d:$ix left
2691 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2693 $w image create end -align center -image $bm -padx 1 \
2695 $w insert end $d [highlight_tag $prefix]
2696 $w mark set s:$ix "end -1c"
2697 $w mark gravity s:$ix left
2702 if {$lev <= $openlevs} {
2705 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2707 $w insert end $tail [highlight_tag $f]
2709 lappend treecontents($prefix) $tail
2712 while {$htstack ne {}} {
2713 set treeheight($prefix) $ht
2714 incr ht [lindex $htstack end]
2715 set htstack [lreplace $htstack end end]
2716 set prefixend [lindex $prefendstack end]
2717 set prefendstack [lreplace $prefendstack end end]
2718 set prefix [string range $prefix 0 $prefixend]
2720 $w conf -state disabled
2723 proc linetoelt {l} {
2724 global treeheight treecontents
2729 foreach e $treecontents($prefix) {
2734 if {[string index $e end] eq "/"} {
2735 set n $treeheight($prefix$e)
2747 proc highlight_tree {y prefix} {
2748 global treeheight treecontents cflist
2750 foreach e $treecontents($prefix) {
2752 if {[highlight_tag $path] ne {}} {
2753 $cflist tag add bold $y.0 "$y.0 lineend"
2756 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2757 set y [highlight_tree $y $path]
2763 proc treeclosedir {w dir} {
2764 global treediropen treeheight treeparent treeindex
2766 set ix $treeindex($dir)
2767 $w conf -state normal
2768 $w delete s:$ix e:$ix
2769 set treediropen($dir) 0
2770 $w image configure a:$ix -image tri-rt
2771 $w conf -state disabled
2772 set n [expr {1 - $treeheight($dir)}]
2773 while {$dir ne {}} {
2774 incr treeheight($dir) $n
2775 set dir $treeparent($dir)
2779 proc treeopendir {w dir} {
2780 global treediropen treeheight treeparent treecontents treeindex
2782 set ix $treeindex($dir)
2783 $w conf -state normal
2784 $w image configure a:$ix -image tri-dn
2785 $w mark set e:$ix s:$ix
2786 $w mark gravity e:$ix right
2789 set n [llength $treecontents($dir)]
2790 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2793 incr treeheight($x) $n
2795 foreach e $treecontents($dir) {
2797 if {[string index $e end] eq "/"} {
2798 set iy $treeindex($de)
2799 $w mark set d:$iy e:$ix
2800 $w mark gravity d:$iy left
2801 $w insert e:$ix $str
2802 set treediropen($de) 0
2803 $w image create e:$ix -align center -image tri-rt -padx 1 \
2805 $w insert e:$ix $e [highlight_tag $de]
2806 $w mark set s:$iy e:$ix
2807 $w mark gravity s:$iy left
2808 set treeheight($de) 1
2810 $w insert e:$ix $str
2811 $w insert e:$ix $e [highlight_tag $de]
2814 $w mark gravity e:$ix right
2815 $w conf -state disabled
2816 set treediropen($dir) 1
2817 set top [lindex [split [$w index @0,0] .] 0]
2818 set ht [$w cget -height]
2819 set l [lindex [split [$w index s:$ix] .] 0]
2822 } elseif {$l + $n + 1 > $top + $ht} {
2823 set top [expr {$l + $n + 2 - $ht}]
2831 proc treeclick {w x y} {
2832 global treediropen cmitmode ctext cflist cflist_top
2834 if {$cmitmode ne "tree"} return
2835 if {![info exists cflist_top]} return
2836 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2837 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2838 $cflist tag add highlight $l.0 "$l.0 lineend"
2844 set e [linetoelt $l]
2845 if {[string index $e end] ne "/"} {
2847 } elseif {$treediropen($e)} {
2854 proc setfilelist {id} {
2855 global treefilelist cflist jump_to_here
2857 treeview $cflist $treefilelist($id) 0
2858 if {$jump_to_here ne {}} {
2859 set f [lindex $jump_to_here 0]
2860 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
2866 image create bitmap tri-rt -background black -foreground blue -data {
2867 #define tri-rt_width 13
2868 #define tri-rt_height 13
2869 static unsigned char tri-rt_bits[] = {
2870 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2871 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2874 #define tri-rt-mask_width 13
2875 #define tri-rt-mask_height 13
2876 static unsigned char tri-rt-mask_bits[] = {
2877 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2878 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2881 image create bitmap tri-dn -background black -foreground blue -data {
2882 #define tri-dn_width 13
2883 #define tri-dn_height 13
2884 static unsigned char tri-dn_bits[] = {
2885 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2886 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2889 #define tri-dn-mask_width 13
2890 #define tri-dn-mask_height 13
2891 static unsigned char tri-dn-mask_bits[] = {
2892 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2893 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2897 image create bitmap reficon-T -background black -foreground yellow -data {
2898 #define tagicon_width 13
2899 #define tagicon_height 9
2900 static unsigned char tagicon_bits[] = {
2901 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2902 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2904 #define tagicon-mask_width 13
2905 #define tagicon-mask_height 9
2906 static unsigned char tagicon-mask_bits[] = {
2907 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2908 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2911 #define headicon_width 13
2912 #define headicon_height 9
2913 static unsigned char headicon_bits[] = {
2914 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2915 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2918 #define headicon-mask_width 13
2919 #define headicon-mask_height 9
2920 static unsigned char headicon-mask_bits[] = {
2921 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2922 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2924 image create bitmap reficon-H -background black -foreground green \
2925 -data $rectdata -maskdata $rectmask
2926 image create bitmap reficon-o -background black -foreground "#ddddff" \
2927 -data $rectdata -maskdata $rectmask
2929 proc init_flist {first} {
2930 global cflist cflist_top difffilestart
2932 $cflist conf -state normal
2933 $cflist delete 0.0 end
2935 $cflist insert end $first
2937 $cflist tag add highlight 1.0 "1.0 lineend"
2939 catch {unset cflist_top}
2941 $cflist conf -state disabled
2942 set difffilestart {}
2945 proc highlight_tag {f} {
2946 global highlight_paths
2948 foreach p $highlight_paths {
2949 if {[string match $p $f]} {
2956 proc highlight_filelist {} {
2957 global cmitmode cflist
2959 $cflist conf -state normal
2960 if {$cmitmode ne "tree"} {
2961 set end [lindex [split [$cflist index end] .] 0]
2962 for {set l 2} {$l < $end} {incr l} {
2963 set line [$cflist get $l.0 "$l.0 lineend"]
2964 if {[highlight_tag $line] ne {}} {
2965 $cflist tag add bold $l.0 "$l.0 lineend"
2971 $cflist conf -state disabled
2974 proc unhighlight_filelist {} {
2977 $cflist conf -state normal
2978 $cflist tag remove bold 1.0 end
2979 $cflist conf -state disabled
2982 proc add_flist {fl} {
2985 $cflist conf -state normal
2987 $cflist insert end "\n"
2988 $cflist insert end $f [highlight_tag $f]
2990 $cflist conf -state disabled
2993 proc sel_flist {w x y} {
2994 global ctext difffilestart cflist cflist_top cmitmode
2996 if {$cmitmode eq "tree"} return
2997 if {![info exists cflist_top]} return
2998 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2999 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3000 $cflist tag add highlight $l.0 "$l.0 lineend"
3005 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
3009 proc pop_flist_menu {w X Y x y} {
3010 global ctext cflist cmitmode flist_menu flist_menu_file
3011 global treediffs diffids
3014 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3016 if {$cmitmode eq "tree"} {
3017 set e [linetoelt $l]
3018 if {[string index $e end] eq "/"} return
3020 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3022 set flist_menu_file $e
3023 set xdiffstate "normal"
3024 if {$cmitmode eq "tree"} {
3025 set xdiffstate "disabled"
3027 # Disable "External diff" item in tree mode
3028 $flist_menu entryconf 2 -state $xdiffstate
3029 tk_popup $flist_menu $X $Y
3032 proc find_ctext_fileinfo {line} {
3033 global ctext_file_names ctext_file_lines
3035 set ok [bsearch $ctext_file_lines $line]
3036 set tline [lindex $ctext_file_lines $ok]
3038 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3041 return [list [lindex $ctext_file_names $ok] $tline]
3045 proc pop_diff_menu {w X Y x y} {
3046 global ctext diff_menu flist_menu_file
3047 global diff_menu_txtpos diff_menu_line
3048 global diff_menu_filebase
3050 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3051 set diff_menu_line [lindex $diff_menu_txtpos 0]
3052 # don't pop up the menu on hunk-separator or file-separator lines
3053 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3057 set f [find_ctext_fileinfo $diff_menu_line]
3058 if {$f eq {}} return
3059 set flist_menu_file [lindex $f 0]
3060 set diff_menu_filebase [lindex $f 1]
3061 tk_popup $diff_menu $X $Y
3064 proc flist_hl {only} {
3065 global flist_menu_file findstring gdttype
3067 set x [shellquote $flist_menu_file]
3068 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3071 append findstring " " $x
3073 set gdttype [mc "touching paths:"]
3076 proc save_file_from_commit {filename output what} {
3079 if {[catch {exec git show $filename -- > $output} err]} {
3080 if {[string match "fatal: bad revision *" $err]} {
3083 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3089 proc external_diff_get_one_file {diffid filename diffdir} {
3090 global nullid nullid2 nullfile
3093 if {$diffid == $nullid} {
3094 set difffile [file join [file dirname $gitdir] $filename]
3095 if {[file exists $difffile]} {
3100 if {$diffid == $nullid2} {
3101 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3102 return [save_file_from_commit :$filename $difffile index]
3104 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3105 return [save_file_from_commit $diffid:$filename $difffile \
3109 proc external_diff {} {
3110 global gitktmpdir nullid nullid2
3111 global flist_menu_file
3114 global gitdir extdifftool
3116 if {[llength $diffids] == 1} {
3117 # no reference commit given
3118 set diffidto [lindex $diffids 0]
3119 if {$diffidto eq $nullid} {
3120 # diffing working copy with index
3121 set diffidfrom $nullid2
3122 } elseif {$diffidto eq $nullid2} {
3123 # diffing index with HEAD
3124 set diffidfrom "HEAD"
3126 # use first parent commit
3127 global parentlist selectedline
3128 set diffidfrom [lindex $parentlist $selectedline 0]
3131 set diffidfrom [lindex $diffids 0]
3132 set diffidto [lindex $diffids 1]
3135 # make sure that several diffs wont collide
3136 if {![info exists gitktmpdir]} {
3137 set gitktmpdir [file join [file dirname $gitdir] \
3138 [format ".gitk-tmp.%s" [pid]]]
3139 if {[catch {file mkdir $gitktmpdir} err]} {
3140 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3147 set diffdir [file join $gitktmpdir $diffnum]
3148 if {[catch {file mkdir $diffdir} err]} {
3149 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3153 # gather files to diff
3154 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3155 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3157 if {$difffromfile ne {} && $difftofile ne {}} {
3158 set cmd [concat | [shellsplit $extdifftool] \
3159 [list $difffromfile $difftofile]]
3160 if {[catch {set fl [open $cmd r]} err]} {
3161 file delete -force $diffdir
3162 error_popup "$extdifftool: [mc "command failed:"] $err"
3164 fconfigure $fl -blocking 0
3165 filerun $fl [list delete_at_eof $fl $diffdir]
3170 proc find_hunk_blamespec {base line} {
3173 # Find and parse the hunk header
3174 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3175 if {$s_lix eq {}} return
3177 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3178 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3179 s_line old_specs osz osz1 new_line nsz]} {
3183 # base lines for the parents
3184 set base_lines [list $new_line]
3185 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3186 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3187 old_spec old_line osz]} {
3190 lappend base_lines $old_line
3193 # Now scan the lines to determine offset within the hunk
3194 set max_parent [expr {[llength $base_lines]-2}]
3196 set s_lno [lindex [split $s_lix "."] 0]
3198 # Determine if the line is removed
3199 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3200 if {[string match {[-+ ]*} $chunk]} {
3201 set removed_idx [string first "-" $chunk]
3202 # Choose a parent index
3203 if {$removed_idx >= 0} {
3204 set parent $removed_idx
3206 set unchanged_idx [string first " " $chunk]
3207 if {$unchanged_idx >= 0} {
3208 set parent $unchanged_idx
3210 # blame the current commit
3214 # then count other lines that belong to it
3215 for {set i $line} {[incr i -1] > $s_lno} {} {
3216 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3217 # Determine if the line is removed
3218 set removed_idx [string first "-" $chunk]
3220 set code [string index $chunk $parent]
3221 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3225 if {$removed_idx < 0} {
3235 incr dline [lindex $base_lines $parent]
3236 return [list $parent $dline]
3239 proc external_blame_diff {} {
3240 global currentid cmitmode
3241 global diff_menu_txtpos diff_menu_line
3242 global diff_menu_filebase flist_menu_file
3244 if {$cmitmode eq "tree"} {
3246 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3248 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3250 set parent_idx [lindex $hinfo 0]
3251 set line [lindex $hinfo 1]
3258 external_blame $parent_idx $line
3261 # Find the SHA1 ID of the blob for file $fname in the index
3263 proc index_sha1 {fname} {
3264 set f [open [list | git ls-files -s $fname] r]
3265 while {[gets $f line] >= 0} {
3266 set info [lindex [split $line "\t"] 0]
3267 set stage [lindex $info 2]
3268 if {$stage eq "0" || $stage eq "2"} {
3270 return [lindex $info 1]
3277 proc external_blame {parent_idx {line {}}} {
3278 global flist_menu_file
3279 global nullid nullid2
3280 global parentlist selectedline currentid
3282 if {$parent_idx > 0} {
3283 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3285 set base_commit $currentid
3288 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3289 error_popup [mc "No such commit"]
3293 set cmdline [list git gui blame]
3294 if {$line ne {} && $line > 1} {
3295 lappend cmdline "--line=$line"
3297 lappend cmdline $base_commit $flist_menu_file
3298 if {[catch {eval exec $cmdline &} err]} {
3299 error_popup "[mc "git gui blame: command failed:"] $err"
3303 proc show_line_source {} {
3304 global cmitmode currentid parents curview blamestuff blameinst
3305 global diff_menu_line diff_menu_filebase flist_menu_file
3306 global nullid nullid2 gitdir
3309 if {$cmitmode eq "tree"} {
3311 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3313 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3314 if {$h eq {}} return
3315 set pi [lindex $h 0]
3317 mark_ctext_line $diff_menu_line
3321 if {$currentid eq $nullid} {
3323 # must be a merge in progress...
3325 # get the last line from .git/MERGE_HEAD
3326 set f [open [file join $gitdir MERGE_HEAD] r]
3327 set id [lindex [split [read $f] "\n"] end-1]
3330 error_popup [mc "Couldn't read merge head: %s" $err]
3333 } elseif {$parents($curview,$currentid) eq $nullid2} {
3334 # need to do the blame from the index
3336 set from_index [index_sha1 $flist_menu_file]
3338 error_popup [mc "Error reading index: %s" $err]
3343 set id [lindex $parents($curview,$currentid) $pi]
3345 set line [lindex $h 1]
3348 if {$from_index ne {}} {
3349 lappend blameargs | git cat-file blob $from_index
3351 lappend blameargs | git blame -p -L$line,+1
3352 if {$from_index ne {}} {
3353 lappend blameargs --contents -
3355 lappend blameargs $id
3357 lappend blameargs -- $flist_menu_file
3359 set f [open $blameargs r]
3361 error_popup [mc "Couldn't start git blame: %s" $err]
3364 fconfigure $f -blocking 0
3365 set i [reg_instance $f]
3366 set blamestuff($i) {}
3368 filerun $f [list read_line_source $f $i]
3371 proc stopblaming {} {
3374 if {[info exists blameinst]} {
3375 stop_instance $blameinst
3380 proc read_line_source {fd inst} {
3381 global blamestuff curview commfd blameinst nullid nullid2
3383 while {[gets $fd line] >= 0} {
3384 lappend blamestuff($inst) $line
3391 fconfigure $fd -blocking 1
3392 if {[catch {close $fd} err]} {
3393 error_popup [mc "Error running git blame: %s" $err]
3398 set line [split [lindex $blamestuff($inst) 0] " "]
3399 set id [lindex $line 0]
3400 set lnum [lindex $line 1]
3401 if {[string length $id] == 40 && [string is xdigit $id] &&
3402 [string is digit -strict $lnum]} {
3403 # look for "filename" line
3404 foreach l $blamestuff($inst) {
3405 if {[string match "filename *" $l]} {
3406 set fname [string range $l 9 end]
3412 # all looks good, select it
3413 if {$id eq $nullid} {
3414 # blame uses all-zeroes to mean not committed,
3415 # which would mean a change in the index
3418 if {[commitinview $id $curview]} {
3419 selectline [rowofcommit $id] 1 [list $fname $lnum]
3421 error_popup [mc "That line comes from commit %s, \
3422 which is not in this view" [shortids $id]]
3425 puts "oops couldn't parse git blame output"
3430 # delete $dir when we see eof on $f (presumably because the child has exited)
3431 proc delete_at_eof {f dir} {
3432 while {[gets $f line] >= 0} {}
3434 if {[catch {close $f} err]} {
3435 error_popup "[mc "External diff viewer failed:"] $err"
3437 file delete -force $dir
3443 # Functions for adding and removing shell-type quoting
3445 proc shellquote {str} {
3446 if {![string match "*\['\"\\ \t]*" $str]} {
3449 if {![string match "*\['\"\\]*" $str]} {
3452 if {![string match "*'*" $str]} {
3455 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3458 proc shellarglist {l} {
3464 append str [shellquote $a]
3469 proc shelldequote {str} {
3474 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3475 append ret [string range $str $used end]
3476 set used [string length $str]
3479 set first [lindex $first 0]
3480 set ch [string index $str $first]
3481 if {$first > $used} {
3482 append ret [string range $str $used [expr {$first - 1}]]
3485 if {$ch eq " " || $ch eq "\t"} break
3488 set first [string first "'" $str $used]
3490 error "unmatched single-quote"
3492 append ret [string range $str $used [expr {$first - 1}]]
3497 if {$used >= [string length $str]} {
3498 error "trailing backslash"
3500 append ret [string index $str $used]
3505 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3506 error "unmatched double-quote"
3508 set first [lindex $first 0]
3509 set ch [string index $str $first]
3510 if {$first > $used} {
3511 append ret [string range $str $used [expr {$first - 1}]]
3514 if {$ch eq "\""} break
3516 append ret [string index $str $used]
3520 return [list $used $ret]
3523 proc shellsplit {str} {
3526 set str [string trimleft $str]
3527 if {$str eq {}} break
3528 set dq [shelldequote $str]
3529 set n [lindex $dq 0]
3530 set word [lindex $dq 1]
3531 set str [string range $str $n end]
3537 # Code to implement multiple views
3539 proc newview {ishighlight} {
3540 global nextviewnum newviewname newishighlight
3541 global revtreeargs viewargscmd newviewopts curview
3543 set newishighlight $ishighlight
3545 if {[winfo exists $top]} {
3549 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3550 set newviewopts($nextviewnum,perm) 0
3551 set newviewopts($nextviewnum,cmd) $viewargscmd($curview)
3552 decode_view_opts $nextviewnum $revtreeargs
3553 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3556 set known_view_options {
3557 {perm b . {} {mc "Remember this view"}}
3558 {args t50= + {} {mc "Commits to include (arguments to git log):"}}
3559 {all b * "--all" {mc "Use all refs"}}
3560 {dorder b . {"--date-order" "-d"} {mc "Strictly sort by date"}}
3561 {lright b . "--left-right" {mc "Mark branch sides"}}
3562 {since t15 + {"--since=*" "--after=*"} {mc "Since date:"}}
3563 {until t15 . {"--until=*" "--before=*"} {mc "Until date:"}}
3564 {limit t10 + "--max-count=*" {mc "Max count:"}}
3565 {skip t10 . "--skip=*" {mc "Skip:"}}
3566 {first b . "--first-parent" {mc "Limit to first parent"}}
3567 {cmd t50= + {} {mc "Command to generate more commits to include:"}}
3570 proc encode_view_opts {n} {
3571 global known_view_options newviewopts
3574 foreach opt $known_view_options {
3575 set patterns [lindex $opt 3]
3576 if {$patterns eq {}} continue
3577 set pattern [lindex $patterns 0]
3579 set val $newviewopts($n,[lindex $opt 0])
3581 if {[lindex $opt 1] eq "b"} {
3583 lappend rargs $pattern
3586 set val [string trim $val]
3588 set pfix [string range $pattern 0 end-1]
3589 lappend rargs $pfix$val
3593 return [concat $rargs [shellsplit $newviewopts($n,args)]]
3596 proc decode_view_opts {n view_args} {
3597 global known_view_options newviewopts
3599 foreach opt $known_view_options {
3600 if {[lindex $opt 1] eq "b"} {
3605 set newviewopts($n,[lindex $opt 0]) $val
3608 foreach arg $view_args {
3609 if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3610 && ![info exists found(limit)]} {
3611 set newviewopts($n,limit) $cnt
3616 foreach opt $known_view_options {
3617 set id [lindex $opt 0]
3618 if {[info exists found($id)]} continue
3619 foreach pattern [lindex $opt 3] {
3620 if {![string match $pattern $arg]} continue
3621 if {[lindex $opt 1] ne "b"} {
3622 set size [string length $pattern]
3623 set val [string range $arg [expr {$size-1}] end]
3627 set newviewopts($n,$id) $val
3631 if {[info exists val]} break
3633 if {[info exists val]} continue
3636 set newviewopts($n,args) [shellarglist $oargs]
3639 proc edit_or_newview {} {
3651 global viewname viewperm newviewname newviewopts
3652 global viewargs viewargscmd
3654 set top .gitkvedit-$curview
3655 if {[winfo exists $top]} {
3659 set newviewname($curview) $viewname($curview)
3660 set newviewopts($curview,perm) $viewperm($curview)
3661 set newviewopts($curview,cmd) $viewargscmd($curview)
3662 decode_view_opts $curview $viewargs($curview)
3663 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3666 proc vieweditor {top n title} {
3667 global newviewname newviewopts viewfiles bgcolor
3668 global known_view_options
3671 wm title $top $title
3676 label $top.nl -text [mc "Name"]
3677 entry $top.name -width 20 -textvariable newviewname($n)
3678 pack $top.nfr -in $top -fill x -pady 5 -padx 3
3679 pack $top.nl -in $top.nfr -side left -padx {0 30}
3680 pack $top.name -in $top.nfr -side left
3686 foreach opt $known_view_options {
3687 set id [lindex $opt 0]
3688 set type [lindex $opt 1]
3689 set flags [lindex $opt 2]
3690 set title [eval [lindex $opt 4]]
3693 if {$flags eq "+" || $flags eq "*"} {
3694 set cframe $top.fr$cnt
3697 pack $cframe -in $top -fill x -pady 3 -padx 3
3698 set cexpand [expr {$flags eq "*"}]
3704 checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
3705 pack $cframe.c_$id -in $cframe -side left \
3706 -padx [list $lxpad 0] -expand $cexpand -anchor w
3707 } elseif {[regexp {^t(\d+)$} $type type sz]} {
3708 message $cframe.l_$id -aspect 1500 -text $title
3709 entry $cframe.e_$id -width $sz -background $bgcolor \
3710 -textvariable newviewopts($n,$id)
3711 pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
3712 pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
3713 } elseif {[regexp {^t(\d+)=$} $type type sz]} {
3714 message $cframe.l_$id -aspect 1500 -text $title
3715 entry $cframe.e_$id -width $sz -background $bgcolor \
3716 -textvariable newviewopts($n,$id)
3717 pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
3718 pack $cframe.e_$id -in $cframe -side top -fill x
3723 message $top.l -aspect 1500 \
3724 -text [mc "Enter files and directories to include, one per line:"]
3725 pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3
3726 text $top.t -width 40 -height 5 -background $bgcolor -font uifont
3727 if {[info exists viewfiles($n)]} {
3728 foreach f $viewfiles($n) {
3729 $top.t insert end $f
3730 $top.t insert end "\n"
3732 $top.t delete {end - 1c} end
3733 $top.t mark set insert 0.0
3735 pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
3737 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3738 button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
3739 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3740 bind $top <Control-Return> [list newviewok $top $n]
3741 bind $top <F5> [list newviewok $top $n 1]
3742 bind $top <Escape> [list destroy $top]
3743 grid $top.buts.ok $top.buts.apply $top.buts.can
3744 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3745 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3746 grid columnconfigure $top.buts 2 -weight 1 -uniform a
3747 pack $top.buts -in $top -side top -fill x
3751 proc doviewmenu {m first cmd op argv} {
3752 set nmenu [$m index end]
3753 for {set i $first} {$i <= $nmenu} {incr i} {
3754 if {[$m entrycget $i -command] eq $cmd} {
3755 eval $m $op $i $argv
3761 proc allviewmenus {n op args} {
3764 doviewmenu .bar.view 5 [list showview $n] $op $args
3765 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3768 proc newviewok {top n {apply 0}} {
3769 global nextviewnum newviewperm newviewname newishighlight
3770 global viewname viewfiles viewperm selectedview curview
3771 global viewargs viewargscmd newviewopts viewhlmenu
3774 set newargs [encode_view_opts $n]
3776 error_popup "[mc "Error in commit selection arguments:"] $err" $top
3780 foreach f [split [$top.t get 0.0 end] "\n"] {
3781 set ft [string trim $f]
3786 if {![info exists viewfiles($n)]} {
3787 # creating a new view
3789 set viewname($n) $newviewname($n)
3790 set viewperm($n) $newviewopts($n,perm)
3791 set viewfiles($n) $files
3792 set viewargs($n) $newargs
3793 set viewargscmd($n) $newviewopts($n,cmd)
3795 if {!$newishighlight} {
3798 run addvhighlight $n
3801 # editing an existing view
3802 set viewperm($n) $newviewopts($n,perm)
3803 if {$newviewname($n) ne $viewname($n)} {
3804 set viewname($n) $newviewname($n)
3805 doviewmenu .bar.view 5 [list showview $n] \
3806 entryconf [list -label $viewname($n)]
3807 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3808 # entryconf [list -label $viewname($n) -value $viewname($n)]
3810 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3811 $newviewopts($n,cmd) ne $viewargscmd($n)} {
3812 set viewfiles($n) $files
3813 set viewargs($n) $newargs
3814 set viewargscmd($n) $newviewopts($n,cmd)
3815 if {$curview == $n} {
3821 catch {destroy $top}
3825 global curview viewperm hlview selectedhlview
3827 if {$curview == 0} return
3828 if {[info exists hlview] && $hlview == $curview} {
3829 set selectedhlview [mc "None"]
3832 allviewmenus $curview delete
3833 set viewperm($curview) 0
3837 proc addviewmenu {n} {
3838 global viewname viewhlmenu
3840 .bar.view add radiobutton -label $viewname($n) \
3841 -command [list showview $n] -variable selectedview -value $n
3842 #$viewhlmenu add radiobutton -label $viewname($n) \
3843 # -command [list addvhighlight $n] -variable selectedhlview
3847 global curview cached_commitrow ordertok
3848 global displayorder parentlist rowidlist rowisopt rowfinal
3849 global colormap rowtextx nextcolor canvxmax
3850 global numcommits viewcomplete
3851 global selectedline currentid canv canvy0
3853 global pending_select mainheadid
3856 global hlview selectedhlview commitinterest
3858 if {$n == $curview} return
3860 set ymax [lindex [$canv cget -scrollregion] 3]
3861 set span [$canv yview]
3862 set ytop [expr {[lindex $span 0] * $ymax}]
3863 set ybot [expr {[lindex $span 1] * $ymax}]
3864 set yscreen [expr {($ybot - $ytop) / 2}]
3865 if {$selectedline ne {}} {
3866 set selid $currentid
3867 set y [yc $selectedline]
3868 if {$ytop < $y && $y < $ybot} {
3869 set yscreen [expr {$y - $ytop}]
3871 } elseif {[info exists pending_select]} {
3872 set selid $pending_select
3873 unset pending_select
3877 catch {unset treediffs}
3879 if {[info exists hlview] && $hlview == $n} {
3881 set selectedhlview [mc "None"]
3883 catch {unset commitinterest}
3884 catch {unset cached_commitrow}
3885 catch {unset ordertok}
3889 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3890 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3893 if {![info exists viewcomplete($n)]} {
3903 set numcommits $commitidx($n)
3905 catch {unset colormap}
3906 catch {unset rowtextx}
3908 set canvxmax [$canv cget -width]
3914 if {$selid ne {} && [commitinview $selid $n]} {
3915 set row [rowofcommit $selid]
3916 # try to get the selected row in the same position on the screen
3917 set ymax [lindex [$canv cget -scrollregion] 3]
3918 set ytop [expr {[yc $row] - $yscreen}]
3922 set yf [expr {$ytop * 1.0 / $ymax}]
3924 allcanvs yview moveto $yf
3928 } elseif {!$viewcomplete($n)} {
3929 reset_pending_select $selid
3931 reset_pending_select {}
3933 if {[commitinview $pending_select $curview]} {
3934 selectline [rowofcommit $pending_select] 1
3936 set row [first_real_row]
3937 if {$row < $numcommits} {
3942 if {!$viewcomplete($n)} {
3943 if {$numcommits == 0} {
3944 show_status [mc "Reading commits..."]
3946 } elseif {$numcommits == 0} {
3947 show_status [mc "No commits selected"]
3951 # Stuff relating to the highlighting facility
3953 proc ishighlighted {id} {
3954 global vhighlights fhighlights nhighlights rhighlights
3956 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3957 return $nhighlights($id)
3959 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3960 return $vhighlights($id)
3962 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3963 return $fhighlights($id)
3965 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3966 return $rhighlights($id)
3971 proc bolden {row font} {
3972 global canv linehtag selectedline boldrows
3974 lappend boldrows $row
3975 $canv itemconf $linehtag($row) -font $font
3976 if {$row == $selectedline} {
3978 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3979 -outline {{}} -tags secsel \
3980 -fill [$canv cget -selectbackground]]
3985 proc bolden_name {row font} {
3986 global canv2 linentag selectedline boldnamerows
3988 lappend boldnamerows $row
3989 $canv2 itemconf $linentag($row) -font $font
3990 if {$row == $selectedline} {
3991 $canv2 delete secsel
3992 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3993 -outline {{}} -tags secsel \
3994 -fill [$canv2 cget -selectbackground]]
4003 foreach row $boldrows {
4004 if {![ishighlighted [commitonrow $row]]} {
4005 bolden $row mainfont
4007 lappend stillbold $row
4010 set boldrows $stillbold
4013 proc addvhighlight {n} {
4014 global hlview viewcomplete curview vhl_done commitidx
4016 if {[info exists hlview]} {
4020 if {$n != $curview && ![info exists viewcomplete($n)]} {
4023 set vhl_done $commitidx($hlview)
4024 if {$vhl_done > 0} {
4029 proc delvhighlight {} {
4030 global hlview vhighlights
4032 if {![info exists hlview]} return
4034 catch {unset vhighlights}
4038 proc vhighlightmore {} {
4039 global hlview vhl_done commitidx vhighlights curview
4041 set max $commitidx($hlview)
4042 set vr [visiblerows]
4043 set r0 [lindex $vr 0]
4044 set r1 [lindex $vr 1]
4045 for {set i $vhl_done} {$i < $max} {incr i} {
4046 set id [commitonrow $i $hlview]
4047 if {[commitinview $id $curview]} {
4048 set row [rowofcommit $id]
4049 if {$r0 <= $row && $row <= $r1} {
4050 if {![highlighted $row]} {
4051 bolden $row mainfontbold
4053 set vhighlights($id) 1
4061 proc askvhighlight {row id} {
4062 global hlview vhighlights iddrawn
4064 if {[commitinview $id $hlview]} {
4065 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
4066 bolden $row mainfontbold
4068 set vhighlights($id) 1
4070 set vhighlights($id) 0
4074 proc hfiles_change {} {
4075 global highlight_files filehighlight fhighlights fh_serial
4076 global highlight_paths gdttype
4078 if {[info exists filehighlight]} {
4079 # delete previous highlights
4080 catch {close $filehighlight}
4082 catch {unset fhighlights}
4084 unhighlight_filelist
4086 set highlight_paths {}
4087 after cancel do_file_hl $fh_serial
4089 if {$highlight_files ne {}} {
4090 after 300 do_file_hl $fh_serial
4094 proc gdttype_change {name ix op} {
4095 global gdttype highlight_files findstring findpattern
4098 if {$findstring ne {}} {
4099 if {$gdttype eq [mc "containing:"]} {
4100 if {$highlight_files ne {}} {
4101 set highlight_files {}
4106 if {$findpattern ne {}} {
4110 set highlight_files $findstring
4115 # enable/disable findtype/findloc menus too
4118 proc find_change {name ix op} {
4119 global gdttype findstring highlight_files
4122 if {$gdttype eq [mc "containing:"]} {
4125 if {$highlight_files ne $findstring} {
4126 set highlight_files $findstring
4133 proc findcom_change args {
4134 global nhighlights boldnamerows
4135 global findpattern findtype findstring gdttype
4138 # delete previous highlights, if any
4139 foreach row $boldnamerows {
4140 bolden_name $row mainfont
4143 catch {unset nhighlights}
4146 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4148 } elseif {$findtype eq [mc "Regexp"]} {
4149 set findpattern $findstring
4151 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4153 set findpattern "*$e*"
4157 proc makepatterns {l} {
4160 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4161 if {[string index $ee end] eq "/"} {
4171 proc do_file_hl {serial} {
4172 global highlight_files filehighlight highlight_paths gdttype fhl_list
4174 if {$gdttype eq [mc "touching paths:"]} {
4175 if {[catch {set paths [shellsplit $highlight_files]}]} return
4176 set highlight_paths [makepatterns $paths]
4178 set gdtargs [concat -- $paths]
4179 } elseif {$gdttype eq [mc "adding/removing string:"]} {
4180 set gdtargs [list "-S$highlight_files"]
4182 # must be "containing:", i.e. we're searching commit info
4185 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4186 set filehighlight [open $cmd r+]
4187 fconfigure $filehighlight -blocking 0
4188 filerun $filehighlight readfhighlight
4194 proc flushhighlights {} {
4195 global filehighlight fhl_list
4197 if {[info exists filehighlight]} {
4199 puts $filehighlight ""
4200 flush $filehighlight
4204 proc askfilehighlight {row id} {
4205 global filehighlight fhighlights fhl_list
4207 lappend fhl_list $id
4208 set fhighlights($id) -1
4209 puts $filehighlight $id
4212 proc readfhighlight {} {
4213 global filehighlight fhighlights curview iddrawn
4214 global fhl_list find_dirn
4216 if {![info exists filehighlight]} {
4220 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4221 set line [string trim $line]
4222 set i [lsearch -exact $fhl_list $line]
4223 if {$i < 0} continue
4224 for {set j 0} {$j < $i} {incr j} {
4225 set id [lindex $fhl_list $j]
4226 set fhighlights($id) 0
4228 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4229 if {$line eq {}} continue
4230 if {![commitinview $line $curview]} continue
4231 set row [rowofcommit $line]
4232 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4233 bolden $row mainfontbold
4235 set fhighlights($line) 1
4237 if {[eof $filehighlight]} {
4239 puts "oops, git diff-tree died"
4240 catch {close $filehighlight}
4244 if {[info exists find_dirn]} {
4250 proc doesmatch {f} {
4251 global findtype findpattern
4253 if {$findtype eq [mc "Regexp"]} {
4254 return [regexp $findpattern $f]
4255 } elseif {$findtype eq [mc "IgnCase"]} {
4256 return [string match -nocase $findpattern $f]
4258 return [string match $findpattern $f]
4262 proc askfindhighlight {row id} {
4263 global nhighlights commitinfo iddrawn
4265 global markingmatches
4267 if {![info exists commitinfo($id)]} {
4270 set info $commitinfo($id)
4272 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4273 foreach f $info ty $fldtypes {
4274 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4276 if {$ty eq [mc "Author"]} {
4283 if {$isbold && [info exists iddrawn($id)]} {
4284 if {![ishighlighted $id]} {
4285 bolden $row mainfontbold
4287 bolden_name $row mainfontbold
4290 if {$markingmatches} {
4291 markrowmatches $row $id
4294 set nhighlights($id) $isbold
4297 proc markrowmatches {row id} {
4298 global canv canv2 linehtag linentag commitinfo findloc
4300 set headline [lindex $commitinfo($id) 0]
4301 set author [lindex $commitinfo($id) 1]
4302 $canv delete match$row
4303 $canv2 delete match$row
4304 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4305 set m [findmatches $headline]
4307 markmatches $canv $row $headline $linehtag($row) $m \
4308 [$canv itemcget $linehtag($row) -font] $row
4311 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4312 set m [findmatches $author]
4314 markmatches $canv2 $row $author $linentag($row) $m \
4315 [$canv2 itemcget $linentag($row) -font] $row
4320 proc vrel_change {name ix op} {
4321 global highlight_related
4324 if {$highlight_related ne [mc "None"]} {
4329 # prepare for testing whether commits are descendents or ancestors of a
4330 proc rhighlight_sel {a} {
4331 global descendent desc_todo ancestor anc_todo
4332 global highlight_related
4334 catch {unset descendent}
4335 set desc_todo [list $a]
4336 catch {unset ancestor}
4337 set anc_todo [list $a]
4338 if {$highlight_related ne [mc "None"]} {
4344 proc rhighlight_none {} {
4347 catch {unset rhighlights}
4351 proc is_descendent {a} {
4352 global curview children descendent desc_todo
4355 set la [rowofcommit $a]
4359 for {set i 0} {$i < [llength $todo]} {incr i} {
4360 set do [lindex $todo $i]
4361 if {[rowofcommit $do] < $la} {
4362 lappend leftover $do
4365 foreach nk $children($v,$do) {
4366 if {![info exists descendent($nk)]} {
4367 set descendent($nk) 1
4375 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4379 set descendent($a) 0
4380 set desc_todo $leftover
4383 proc is_ancestor {a} {
4384 global curview parents ancestor anc_todo
4387 set la [rowofcommit $a]
4391 for {set i 0} {$i < [llength $todo]} {incr i} {
4392 set do [lindex $todo $i]
4393 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4394 lappend leftover $do
4397 foreach np $parents($v,$do) {
4398 if {![info exists ancestor($np)]} {
4407 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4412 set anc_todo $leftover
4415 proc askrelhighlight {row id} {
4416 global descendent highlight_related iddrawn rhighlights
4417 global selectedline ancestor
4419 if {$selectedline eq {}} return
4421 if {$highlight_related eq [mc "Descendant"] ||
4422 $highlight_related eq [mc "Not descendant"]} {
4423 if {![info exists descendent($id)]} {
4426 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4429 } elseif {$highlight_related eq [mc "Ancestor"] ||
4430 $highlight_related eq [mc "Not ancestor"]} {
4431 if {![info exists ancestor($id)]} {
4434 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4438 if {[info exists iddrawn($id)]} {
4439 if {$isbold && ![ishighlighted $id]} {
4440 bolden $row mainfontbold
4443 set rhighlights($id) $isbold
4446 # Graph layout functions
4448 proc shortids {ids} {
4451 if {[llength $id] > 1} {
4452 lappend res [shortids $id]
4453 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4454 lappend res [string range $id 0 7]
4465 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4466 if {($n & $mask) != 0} {
4467 set ret [concat $ret $o]
4469 set o [concat $o $o]
4474 proc ordertoken {id} {
4475 global ordertok curview varcid varcstart varctok curview parents children
4476 global nullid nullid2
4478 if {[info exists ordertok($id)]} {
4479 return $ordertok($id)
4484 if {[info exists varcid($curview,$id)]} {
4485 set a $varcid($curview,$id)
4486 set p [lindex $varcstart($curview) $a]
4488 set p [lindex $children($curview,$id) 0]
4490 if {[info exists ordertok($p)]} {
4491 set tok $ordertok($p)
4494 set id [first_real_child $curview,$p]
4497 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4500 if {[llength $parents($curview,$id)] == 1} {
4501 lappend todo [list $p {}]
4503 set j [lsearch -exact $parents($curview,$id) $p]
4505 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4507 lappend todo [list $p [strrep $j]]
4510 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4511 set p [lindex $todo $i 0]
4512 append tok [lindex $todo $i 1]
4513 set ordertok($p) $tok
4515 set ordertok($origid) $tok
4519 # Work out where id should go in idlist so that order-token
4520 # values increase from left to right
4521 proc idcol {idlist id {i 0}} {
4522 set t [ordertoken $id]
4526 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4527 if {$i > [llength $idlist]} {
4528 set i [llength $idlist]
4530 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4533 if {$t > [ordertoken [lindex $idlist $i]]} {
4534 while {[incr i] < [llength $idlist] &&
4535 $t >= [ordertoken [lindex $idlist $i]]} {}
4541 proc initlayout {} {
4542 global rowidlist rowisopt rowfinal displayorder parentlist
4543 global numcommits canvxmax canv
4545 global colormap rowtextx
4554 set canvxmax [$canv cget -width]
4555 catch {unset colormap}
4556 catch {unset rowtextx}
4560 proc setcanvscroll {} {
4561 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4562 global lastscrollset lastscrollrows
4564 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4565 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4566 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4567 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4568 set lastscrollset [clock clicks -milliseconds]
4569 set lastscrollrows $numcommits
4572 proc visiblerows {} {
4573 global canv numcommits linespc
4575 set ymax [lindex [$canv cget -scrollregion] 3]
4576 if {$ymax eq {} || $ymax == 0} return
4578 set y0 [expr {int([lindex $f 0] * $ymax)}]
4579 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4583 set y1 [expr {int([lindex $f 1] * $ymax)}]
4584 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4585 if {$r1 >= $numcommits} {
4586 set r1 [expr {$numcommits - 1}]
4588 return [list $r0 $r1]
4591 proc layoutmore {} {
4592 global commitidx viewcomplete curview
4593 global numcommits pending_select curview
4594 global lastscrollset lastscrollrows
4596 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4597 [clock clicks -milliseconds] - $lastscrollset > 500} {
4600 if {[info exists pending_select] &&
4601 [commitinview $pending_select $curview]} {
4603 selectline [rowofcommit $pending_select] 1
4608 proc doshowlocalchanges {} {
4609 global curview mainheadid
4611 if {$mainheadid eq {}} return
4612 if {[commitinview $mainheadid $curview]} {
4615 interestedin $mainheadid dodiffindex
4619 proc dohidelocalchanges {} {
4620 global nullid nullid2 lserial curview
4622 if {[commitinview $nullid $curview]} {
4623 removefakerow $nullid
4625 if {[commitinview $nullid2 $curview]} {
4626 removefakerow $nullid2
4631 # spawn off a process to do git diff-index --cached HEAD
4632 proc dodiffindex {} {
4633 global lserial showlocalchanges
4636 if {!$showlocalchanges || !$isworktree} return
4638 set fd [open "|git diff-index --cached HEAD" r]
4639 fconfigure $fd -blocking 0
4640 set i [reg_instance $fd]
4641 filerun $fd [list readdiffindex $fd $lserial $i]
4644 proc readdiffindex {fd serial inst} {
4645 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4648 if {[gets $fd line] < 0} {
4654 # we only need to see one line and we don't really care what it says...
4657 if {$serial != $lserial} {
4661 # now see if there are any local changes not checked in to the index
4662 set fd [open "|git diff-files" r]
4663 fconfigure $fd -blocking 0
4664 set i [reg_instance $fd]
4665 filerun $fd [list readdifffiles $fd $serial $i]
4667 if {$isdiff && ![commitinview $nullid2 $curview]} {
4668 # add the line for the changes in the index to the graph
4669 set hl [mc "Local changes checked in to index but not committed"]
4670 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4671 set commitdata($nullid2) "\n $hl\n"
4672 if {[commitinview $nullid $curview]} {
4673 removefakerow $nullid
4675 insertfakerow $nullid2 $mainheadid
4676 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4677 removefakerow $nullid2
4682 proc readdifffiles {fd serial inst} {
4683 global mainheadid nullid nullid2 curview
4684 global commitinfo commitdata lserial
4687 if {[gets $fd line] < 0} {
4693 # we only need to see one line and we don't really care what it says...
4696 if {$serial != $lserial} {
4700 if {$isdiff && ![commitinview $nullid $curview]} {
4701 # add the line for the local diff to the graph
4702 set hl [mc "Local uncommitted changes, not checked in to index"]
4703 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4704 set commitdata($nullid) "\n $hl\n"
4705 if {[commitinview $nullid2 $curview]} {
4710 insertfakerow $nullid $p
4711 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4712 removefakerow $nullid
4717 proc nextuse {id row} {
4718 global curview children
4720 if {[info exists children($curview,$id)]} {
4721 foreach kid $children($curview,$id) {
4722 if {![commitinview $kid $curview]} {
4725 if {[rowofcommit $kid] > $row} {
4726 return [rowofcommit $kid]
4730 if {[commitinview $id $curview]} {
4731 return [rowofcommit $id]
4736 proc prevuse {id row} {
4737 global curview children
4740 if {[info exists children($curview,$id)]} {
4741 foreach kid $children($curview,$id) {
4742 if {![commitinview $kid $curview]} break
4743 if {[rowofcommit $kid] < $row} {
4744 set ret [rowofcommit $kid]
4751 proc make_idlist {row} {
4752 global displayorder parentlist uparrowlen downarrowlen mingaplen
4753 global commitidx curview children
4755 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4759 set ra [expr {$row - $downarrowlen}]
4763 set rb [expr {$row + $uparrowlen}]
4764 if {$rb > $commitidx($curview)} {
4765 set rb $commitidx($curview)
4767 make_disporder $r [expr {$rb + 1}]
4769 for {} {$r < $ra} {incr r} {
4770 set nextid [lindex $displayorder [expr {$r + 1}]]
4771 foreach p [lindex $parentlist $r] {
4772 if {$p eq $nextid} continue
4773 set rn [nextuse $p $r]
4775 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4776 lappend ids [list [ordertoken $p] $p]
4780 for {} {$r < $row} {incr r} {
4781 set nextid [lindex $displayorder [expr {$r + 1}]]
4782 foreach p [lindex $parentlist $r] {
4783 if {$p eq $nextid} continue
4784 set rn [nextuse $p $r]
4785 if {$rn < 0 || $rn >= $row} {
4786 lappend ids [list [ordertoken $p] $p]
4790 set id [lindex $displayorder $row]
4791 lappend ids [list [ordertoken $id] $id]
4793 foreach p [lindex $parentlist $r] {
4794 set firstkid [lindex $children($curview,$p) 0]
4795 if {[rowofcommit $firstkid] < $row} {
4796 lappend ids [list [ordertoken $p] $p]
4800 set id [lindex $displayorder $r]
4802 set firstkid [lindex $children($curview,$id) 0]
4803 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4804 lappend ids [list [ordertoken $id] $id]
4809 foreach idx [lsort -unique $ids] {
4810 lappend idlist [lindex $idx 1]
4815 proc rowsequal {a b} {
4816 while {[set i [lsearch -exact $a {}]] >= 0} {
4817 set a [lreplace $a $i $i]
4819 while {[set i [lsearch -exact $b {}]] >= 0} {
4820 set b [lreplace $b $i $i]
4822 return [expr {$a eq $b}]
4825 proc makeupline {id row rend col} {
4826 global rowidlist uparrowlen downarrowlen mingaplen
4828 for {set r $rend} {1} {set r $rstart} {
4829 set rstart [prevuse $id $r]
4830 if {$rstart < 0} return
4831 if {$rstart < $row} break
4833 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4834 set rstart [expr {$rend - $uparrowlen - 1}]
4836 for {set r $rstart} {[incr r] <= $row} {} {
4837 set idlist [lindex $rowidlist $r]
4838 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4839 set col [idcol $idlist $id $col]
4840 lset rowidlist $r [linsert $idlist $col $id]
4846 proc layoutrows {row endrow} {
4847 global rowidlist rowisopt rowfinal displayorder
4848 global uparrowlen downarrowlen maxwidth mingaplen
4849 global children parentlist
4850 global commitidx viewcomplete curview
4852 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4855 set rm1 [expr {$row - 1}]
4856 foreach id [lindex $rowidlist $rm1] {
4861 set final [lindex $rowfinal $rm1]
4863 for {} {$row < $endrow} {incr row} {
4864 set rm1 [expr {$row - 1}]
4865 if {$rm1 < 0 || $idlist eq {}} {
4866 set idlist [make_idlist $row]
4869 set id [lindex $displayorder $rm1]
4870 set col [lsearch -exact $idlist $id]
4871 set idlist [lreplace $idlist $col $col]
4872 foreach p [lindex $parentlist $rm1] {
4873 if {[lsearch -exact $idlist $p] < 0} {
4874 set col [idcol $idlist $p $col]
4875 set idlist [linsert $idlist $col $p]
4876 # if not the first child, we have to insert a line going up
4877 if {$id ne [lindex $children($curview,$p) 0]} {
4878 makeupline $p $rm1 $row $col
4882 set id [lindex $displayorder $row]
4883 if {$row > $downarrowlen} {
4884 set termrow [expr {$row - $downarrowlen - 1}]
4885 foreach p [lindex $parentlist $termrow] {
4886 set i [lsearch -exact $idlist $p]
4887 if {$i < 0} continue
4888 set nr [nextuse $p $termrow]
4889 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4890 set idlist [lreplace $idlist $i $i]
4894 set col [lsearch -exact $idlist $id]
4896 set col [idcol $idlist $id]
4897 set idlist [linsert $idlist $col $id]
4898 if {$children($curview,$id) ne {}} {
4899 makeupline $id $rm1 $row $col
4902 set r [expr {$row + $uparrowlen - 1}]
4903 if {$r < $commitidx($curview)} {
4905 foreach p [lindex $parentlist $r] {
4906 if {[lsearch -exact $idlist $p] >= 0} continue
4907 set fk [lindex $children($curview,$p) 0]
4908 if {[rowofcommit $fk] < $row} {
4909 set x [idcol $idlist $p $x]
4910 set idlist [linsert $idlist $x $p]
4913 if {[incr r] < $commitidx($curview)} {
4914 set p [lindex $displayorder $r]
4915 if {[lsearch -exact $idlist $p] < 0} {
4916 set fk [lindex $children($curview,$p) 0]
4917 if {$fk ne {} && [rowofcommit $fk] < $row} {
4918 set x [idcol $idlist $p $x]
4919 set idlist [linsert $idlist $x $p]
4925 if {$final && !$viewcomplete($curview) &&
4926 $row + $uparrowlen + $mingaplen + $downarrowlen
4927 >= $commitidx($curview)} {
4930 set l [llength $rowidlist]
4932 lappend rowidlist $idlist
4934 lappend rowfinal $final
4935 } elseif {$row < $l} {
4936 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4937 lset rowidlist $row $idlist
4940 lset rowfinal $row $final
4942 set pad [ntimes [expr {$row - $l}] {}]
4943 set rowidlist [concat $rowidlist $pad]
4944 lappend rowidlist $idlist
4945 set rowfinal [concat $rowfinal $pad]
4946 lappend rowfinal $final
4947 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4953 proc changedrow {row} {
4954 global displayorder iddrawn rowisopt need_redisplay
4956 set l [llength $rowisopt]
4958 lset rowisopt $row 0
4959 if {$row + 1 < $l} {
4960 lset rowisopt [expr {$row + 1}] 0
4961 if {$row + 2 < $l} {
4962 lset rowisopt [expr {$row + 2}] 0
4966 set id [lindex $displayorder $row]
4967 if {[info exists iddrawn($id)]} {
4968 set need_redisplay 1
4972 proc insert_pad {row col npad} {
4975 set pad [ntimes $npad {}]
4976 set idlist [lindex $rowidlist $row]
4977 set bef [lrange $idlist 0 [expr {$col - 1}]]
4978 set aft [lrange $idlist $col end]
4979 set i [lsearch -exact $aft {}]
4981 set aft [lreplace $aft $i $i]
4983 lset rowidlist $row [concat $bef $pad $aft]
4987 proc optimize_rows {row col endrow} {
4988 global rowidlist rowisopt displayorder curview children
4993 for {} {$row < $endrow} {incr row; set col 0} {
4994 if {[lindex $rowisopt $row]} continue
4996 set y0 [expr {$row - 1}]
4997 set ym [expr {$row - 2}]
4998 set idlist [lindex $rowidlist $row]
4999 set previdlist [lindex $rowidlist $y0]
5000 if {$idlist eq {} || $previdlist eq {}} continue
5002 set pprevidlist [lindex $rowidlist $ym]
5003 if {$pprevidlist eq {}} continue
5009 for {} {$col < [llength $idlist]} {incr col} {
5010 set id [lindex $idlist $col]
5011 if {[lindex $previdlist $col] eq $id} continue
5016 set x0 [lsearch -exact $previdlist $id]
5017 if {$x0 < 0} continue
5018 set z [expr {$x0 - $col}]
5022 set xm [lsearch -exact $pprevidlist $id]
5024 set z0 [expr {$xm - $x0}]
5028 # if row y0 is the first child of $id then it's not an arrow
5029 if {[lindex $children($curview,$id) 0] ne
5030 [lindex $displayorder $y0]} {
5034 if {!$isarrow && $id ne [lindex $displayorder $row] &&
5035 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
5038 # Looking at lines from this row to the previous row,
5039 # make them go straight up if they end in an arrow on
5040 # the previous row; otherwise make them go straight up
5042 if {$z < -1 || ($z < 0 && $isarrow)} {
5043 # Line currently goes left too much;
5044 # insert pads in the previous row, then optimize it
5045 set npad [expr {-1 - $z + $isarrow}]
5046 insert_pad $y0 $x0 $npad
5048 optimize_rows $y0 $x0 $row
5050 set previdlist [lindex $rowidlist $y0]
5051 set x0 [lsearch -exact $previdlist $id]
5052 set z [expr {$x0 - $col}]
5054 set pprevidlist [lindex $rowidlist $ym]
5055 set xm [lsearch -exact $pprevidlist $id]
5056 set z0 [expr {$xm - $x0}]
5058 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
5059 # Line currently goes right too much;
5060 # insert pads in this line
5061 set npad [expr {$z - 1 + $isarrow}]
5062 insert_pad $row $col $npad
5063 set idlist [lindex $rowidlist $row]
5065 set z [expr {$x0 - $col}]
5068 if {$z0 eq {} && !$isarrow && $ym >= 0} {
5069 # this line links to its first child on row $row-2
5070 set id [lindex $displayorder $ym]
5071 set xc [lsearch -exact $pprevidlist $id]
5073 set z0 [expr {$xc - $x0}]
5076 # avoid lines jigging left then immediately right
5077 if {$z0 ne {} && $z < 0 && $z0 > 0} {
5078 insert_pad $y0 $x0 1
5080 optimize_rows $y0 $x0 $row
5081 set previdlist [lindex $rowidlist $y0]
5085 # Find the first column that doesn't have a line going right
5086 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5087 set id [lindex $idlist $col]
5088 if {$id eq {}} break
5089 set x0 [lsearch -exact $previdlist $id]
5091 # check if this is the link to the first child
5092 set kid [lindex $displayorder $y0]
5093 if {[lindex $children($curview,$id) 0] eq $kid} {
5094 # it is, work out offset to child
5095 set x0 [lsearch -exact $previdlist $kid]
5098 if {$x0 <= $col} break
5100 # Insert a pad at that column as long as it has a line and
5101 # isn't the last column
5102 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5103 set idlist [linsert $idlist $col {}]
5104 lset rowidlist $row $idlist
5112 global canvx0 linespc
5113 return [expr {$canvx0 + $col * $linespc}]
5117 global canvy0 linespc
5118 return [expr {$canvy0 + $row * $linespc}]
5121 proc linewidth {id} {
5122 global thickerline lthickness
5125 if {[info exists thickerline] && $id eq $thickerline} {
5126 set wid [expr {2 * $lthickness}]
5131 proc rowranges {id} {
5132 global curview children uparrowlen downarrowlen
5135 set kids $children($curview,$id)
5141 foreach child $kids {
5142 if {![commitinview $child $curview]} break
5143 set row [rowofcommit $child]
5144 if {![info exists prev]} {
5145 lappend ret [expr {$row + 1}]
5147 if {$row <= $prevrow} {
5148 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5150 # see if the line extends the whole way from prevrow to row
5151 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5152 [lsearch -exact [lindex $rowidlist \
5153 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5154 # it doesn't, see where it ends
5155 set r [expr {$prevrow + $downarrowlen}]
5156 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5157 while {[incr r -1] > $prevrow &&
5158 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5160 while {[incr r] <= $row &&
5161 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5165 # see where it starts up again
5166 set r [expr {$row - $uparrowlen}]
5167 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5168 while {[incr r] < $row &&
5169 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5171 while {[incr r -1] >= $prevrow &&
5172 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5178 if {$child eq $id} {
5187 proc drawlineseg {id row endrow arrowlow} {
5188 global rowidlist displayorder iddrawn linesegs
5189 global canv colormap linespc curview maxlinelen parentlist
5191 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5192 set le [expr {$row + 1}]
5195 set c [lsearch -exact [lindex $rowidlist $le] $id]
5201 set x [lindex $displayorder $le]
5206 if {[info exists iddrawn($x)] || $le == $endrow} {
5207 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5223 if {[info exists linesegs($id)]} {
5224 set lines $linesegs($id)
5226 set r0 [lindex $li 0]
5228 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5238 set li [lindex $lines [expr {$i-1}]]
5239 set r1 [lindex $li 1]
5240 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5245 set x [lindex $cols [expr {$le - $row}]]
5246 set xp [lindex $cols [expr {$le - 1 - $row}]]
5247 set dir [expr {$xp - $x}]
5249 set ith [lindex $lines $i 2]
5250 set coords [$canv coords $ith]
5251 set ah [$canv itemcget $ith -arrow]
5252 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5253 set x2 [lindex $cols [expr {$le + 1 - $row}]]
5254 if {$x2 ne {} && $x - $x2 == $dir} {
5255 set coords [lrange $coords 0 end-2]
5258 set coords [list [xc $le $x] [yc $le]]
5261 set itl [lindex $lines [expr {$i-1}] 2]
5262 set al [$canv itemcget $itl -arrow]
5263 set arrowlow [expr {$al eq "last" || $al eq "both"}]
5264 } elseif {$arrowlow} {
5265 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5266 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5270 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5271 for {set y $le} {[incr y -1] > $row} {} {
5273 set xp [lindex $cols [expr {$y - 1 - $row}]]
5274 set ndir [expr {$xp - $x}]
5275 if {$dir != $ndir || $xp < 0} {
5276 lappend coords [xc $y $x] [yc $y]
5282 # join parent line to first child
5283 set ch [lindex $displayorder $row]
5284 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5286 puts "oops: drawlineseg: child $ch not on row $row"
5287 } elseif {$xc != $x} {
5288 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5289 set d [expr {int(0.5 * $linespc)}]
5292 set x2 [expr {$x1 - $d}]
5294 set x2 [expr {$x1 + $d}]
5297 set y1 [expr {$y2 + $d}]
5298 lappend coords $x1 $y1 $x2 $y2
5299 } elseif {$xc < $x - 1} {
5300 lappend coords [xc $row [expr {$x-1}]] [yc $row]
5301 } elseif {$xc > $x + 1} {
5302 lappend coords [xc $row [expr {$x+1}]] [yc $row]
5306 lappend coords [xc $row $x] [yc $row]
5308 set xn [xc $row $xp]
5310 lappend coords $xn $yn
5314 set t [$canv create line $coords -width [linewidth $id] \
5315 -fill $colormap($id) -tags lines.$id -arrow $arrow]
5318 set lines [linsert $lines $i [list $row $le $t]]
5320 $canv coords $ith $coords
5321 if {$arrow ne $ah} {
5322 $canv itemconf $ith -arrow $arrow
5324 lset lines $i 0 $row
5327 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5328 set ndir [expr {$xo - $xp}]
5329 set clow [$canv coords $itl]
5330 if {$dir == $ndir} {
5331 set clow [lrange $clow 2 end]
5333 set coords [concat $coords $clow]
5335 lset lines [expr {$i-1}] 1 $le
5337 # coalesce two pieces
5339 set b [lindex $lines [expr {$i-1}] 0]
5340 set e [lindex $lines $i 1]
5341 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5343 $canv coords $itl $coords
5344 if {$arrow ne $al} {
5345 $canv itemconf $itl -arrow $arrow
5349 set linesegs($id) $lines
5353 proc drawparentlinks {id row} {
5354 global rowidlist canv colormap curview parentlist
5355 global idpos linespc
5357 set rowids [lindex $rowidlist $row]
5358 set col [lsearch -exact $rowids $id]
5359 if {$col < 0} return
5360 set olds [lindex $parentlist $row]
5361 set row2 [expr {$row + 1}]
5362 set x [xc $row $col]
5365 set d [expr {int(0.5 * $linespc)}]
5366 set ymid [expr {$y + $d}]
5367 set ids [lindex $rowidlist $row2]
5368 # rmx = right-most X coord used
5371 set i [lsearch -exact $ids $p]
5373 puts "oops, parent $p of $id not in list"
5376 set x2 [xc $row2 $i]
5380 set j [lsearch -exact $rowids $p]
5382 # drawlineseg will do this one for us
5386 # should handle duplicated parents here...
5387 set coords [list $x $y]
5389 # if attaching to a vertical segment, draw a smaller
5390 # slant for visual distinctness
5393 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5395 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5397 } elseif {$i < $col && $i < $j} {
5398 # segment slants towards us already
5399 lappend coords [xc $row $j] $y
5401 if {$i < $col - 1} {
5402 lappend coords [expr {$x2 + $linespc}] $y
5403 } elseif {$i > $col + 1} {
5404 lappend coords [expr {$x2 - $linespc}] $y
5406 lappend coords $x2 $y2
5409 lappend coords $x2 $y2
5411 set t [$canv create line $coords -width [linewidth $p] \
5412 -fill $colormap($p) -tags lines.$p]
5416 if {$rmx > [lindex $idpos($id) 1]} {
5417 lset idpos($id) 1 $rmx
5422 proc drawlines {id} {
5425 $canv itemconf lines.$id -width [linewidth $id]
5428 proc drawcmittext {id row col} {
5429 global linespc canv canv2 canv3 fgcolor curview
5430 global cmitlisted commitinfo rowidlist parentlist
5431 global rowtextx idpos idtags idheads idotherrefs
5432 global linehtag linentag linedtag selectedline
5433 global canvxmax boldrows boldnamerows fgcolor
5434 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5436 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5437 set listed $cmitlisted($curview,$id)
5438 if {$id eq $nullid} {
5440 } elseif {$id eq $nullid2} {
5442 } elseif {$id eq $mainheadid} {
5445 set ofill [lindex $circlecolors $listed]
5447 set x [xc $row $col]
5449 set orad [expr {$linespc / 3}]
5451 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5452 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5453 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5454 } elseif {$listed == 3} {
5455 # triangle pointing left for left-side commits
5456 set t [$canv create polygon \
5457 [expr {$x - $orad}] $y \
5458 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5459 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5460 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5462 # triangle pointing right for right-side commits
5463 set t [$canv create polygon \
5464 [expr {$x + $orad - 1}] $y \
5465 [expr {$x - $orad}] [expr {$y - $orad}] \
5466 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5467 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5469 set circleitem($row) $t
5471 $canv bind $t <1> {selcanvline {} %x %y}
5472 set rmx [llength [lindex $rowidlist $row]]
5473 set olds [lindex $parentlist $row]
5475 set nextids [lindex $rowidlist [expr {$row + 1}]]
5477 set i [lsearch -exact $nextids $p]
5483 set xt [xc $row $rmx]
5484 set rowtextx($row) $xt
5485 set idpos($id) [list $x $xt $y]
5486 if {[info exists idtags($id)] || [info exists idheads($id)]
5487 || [info exists idotherrefs($id)]} {
5488 set xt [drawtags $id $x $xt $y]
5490 set headline [lindex $commitinfo($id) 0]
5491 set name [lindex $commitinfo($id) 1]
5492 set date [lindex $commitinfo($id) 2]
5493 set date [formatdate $date]
5496 set isbold [ishighlighted $id]
5498 lappend boldrows $row
5499 set font mainfontbold
5501 lappend boldnamerows $row
5502 set nfont mainfontbold
5505 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
5506 -text $headline -font $font -tags text]
5507 $canv bind $linehtag($row) $ctxbut "rowmenu %X %Y $id"
5508 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5509 -text $name -font $nfont -tags text]
5510 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5511 -text $date -font mainfont -tags text]
5512 if {$selectedline == $row} {
5515 set xr [expr {$xt + [font measure $font $headline]}]
5516 if {$xr > $canvxmax} {
5522 proc drawcmitrow {row} {
5523 global displayorder rowidlist nrows_drawn
5524 global iddrawn markingmatches
5525 global commitinfo numcommits
5526 global filehighlight fhighlights findpattern nhighlights
5527 global hlview vhighlights
5528 global highlight_related rhighlights
5530 if {$row >= $numcommits} return
5532 set id [lindex $displayorder $row]
5533 if {[info exists hlview] && ![info exists vhighlights($id)]} {
5534 askvhighlight $row $id
5536 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5537 askfilehighlight $row $id
5539 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5540 askfindhighlight $row $id
5542 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5543 askrelhighlight $row $id
5545 if {![info exists iddrawn($id)]} {
5546 set col [lsearch -exact [lindex $rowidlist $row] $id]
5548 puts "oops, row $row id $id not in list"
5551 if {![info exists commitinfo($id)]} {
5555 drawcmittext $id $row $col
5559 if {$markingmatches} {
5560 markrowmatches $row $id
5564 proc drawcommits {row {endrow {}}} {
5565 global numcommits iddrawn displayorder curview need_redisplay
5566 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5571 if {$endrow eq {}} {
5574 if {$endrow >= $numcommits} {
5575 set endrow [expr {$numcommits - 1}]
5578 set rl1 [expr {$row - $downarrowlen - 3}]
5582 set ro1 [expr {$row - 3}]
5586 set r2 [expr {$endrow + $uparrowlen + 3}]
5587 if {$r2 > $numcommits} {
5590 for {set r $rl1} {$r < $r2} {incr r} {
5591 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5595 set rl1 [expr {$r + 1}]
5601 optimize_rows $ro1 0 $r2
5602 if {$need_redisplay || $nrows_drawn > 2000} {
5607 # make the lines join to already-drawn rows either side
5608 set r [expr {$row - 1}]
5609 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5612 set er [expr {$endrow + 1}]
5613 if {$er >= $numcommits ||
5614 ![info exists iddrawn([lindex $displayorder $er])]} {
5617 for {} {$r <= $er} {incr r} {
5618 set id [lindex $displayorder $r]
5619 set wasdrawn [info exists iddrawn($id)]
5621 if {$r == $er} break
5622 set nextid [lindex $displayorder [expr {$r + 1}]]
5623 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5624 drawparentlinks $id $r
5626 set rowids [lindex $rowidlist $r]
5627 foreach lid $rowids {
5628 if {$lid eq {}} continue
5629 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5631 # see if this is the first child of any of its parents
5632 foreach p [lindex $parentlist $r] {
5633 if {[lsearch -exact $rowids $p] < 0} {
5634 # make this line extend up to the child
5635 set lineend($p) [drawlineseg $p $r $er 0]
5639 set lineend($lid) [drawlineseg $lid $r $er 1]
5645 proc undolayout {row} {
5646 global uparrowlen mingaplen downarrowlen
5647 global rowidlist rowisopt rowfinal need_redisplay
5649 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5653 if {[llength $rowidlist] > $r} {
5655 set rowidlist [lrange $rowidlist 0 $r]
5656 set rowfinal [lrange $rowfinal 0 $r]
5657 set rowisopt [lrange $rowisopt 0 $r]
5658 set need_redisplay 1
5663 proc drawvisible {} {
5664 global canv linespc curview vrowmod selectedline targetrow targetid
5665 global need_redisplay cscroll numcommits
5667 set fs [$canv yview]
5668 set ymax [lindex [$canv cget -scrollregion] 3]
5669 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5670 set f0 [lindex $fs 0]
5671 set f1 [lindex $fs 1]
5672 set y0 [expr {int($f0 * $ymax)}]
5673 set y1 [expr {int($f1 * $ymax)}]
5675 if {[info exists targetid]} {
5676 if {[commitinview $targetid $curview]} {
5677 set r [rowofcommit $targetid]
5678 if {$r != $targetrow} {
5679 # Fix up the scrollregion and change the scrolling position
5680 # now that our target row has moved.
5681 set diff [expr {($r - $targetrow) * $linespc}]
5684 set ymax [lindex [$canv cget -scrollregion] 3]
5687 set f0 [expr {$y0 / $ymax}]
5688 set f1 [expr {$y1 / $ymax}]
5689 allcanvs yview moveto $f0
5690 $cscroll set $f0 $f1
5691 set need_redisplay 1
5698 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5699 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5700 if {$endrow >= $vrowmod($curview)} {
5701 update_arcrows $curview
5703 if {$selectedline ne {} &&
5704 $row <= $selectedline && $selectedline <= $endrow} {
5705 set targetrow $selectedline
5706 } elseif {[info exists targetid]} {
5707 set targetrow [expr {int(($row + $endrow) / 2)}]
5709 if {[info exists targetrow]} {
5710 if {$targetrow >= $numcommits} {
5711 set targetrow [expr {$numcommits - 1}]
5713 set targetid [commitonrow $targetrow]
5715 drawcommits $row $endrow
5718 proc clear_display {} {
5719 global iddrawn linesegs need_redisplay nrows_drawn
5720 global vhighlights fhighlights nhighlights rhighlights
5721 global linehtag linentag linedtag boldrows boldnamerows
5724 catch {unset iddrawn}
5725 catch {unset linesegs}
5726 catch {unset linehtag}
5727 catch {unset linentag}
5728 catch {unset linedtag}
5731 catch {unset vhighlights}
5732 catch {unset fhighlights}
5733 catch {unset nhighlights}
5734 catch {unset rhighlights}
5735 set need_redisplay 0
5739 proc findcrossings {id} {
5740 global rowidlist parentlist numcommits displayorder
5744 foreach {s e} [rowranges $id] {
5745 if {$e >= $numcommits} {
5746 set e [expr {$numcommits - 1}]
5748 if {$e <= $s} continue
5749 for {set row $e} {[incr row -1] >= $s} {} {
5750 set x [lsearch -exact [lindex $rowidlist $row] $id]
5752 set olds [lindex $parentlist $row]
5753 set kid [lindex $displayorder $row]
5754 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5755 if {$kidx < 0} continue
5756 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5758 set px [lsearch -exact $nextrow $p]
5759 if {$px < 0} continue
5760 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5761 if {[lsearch -exact $ccross $p] >= 0} continue
5762 if {$x == $px + ($kidx < $px? -1: 1)} {
5764 } elseif {[lsearch -exact $cross $p] < 0} {
5771 return [concat $ccross {{}} $cross]
5774 proc assigncolor {id} {
5775 global colormap colors nextcolor
5776 global parents children children curview
5778 if {[info exists colormap($id)]} return
5779 set ncolors [llength $colors]
5780 if {[info exists children($curview,$id)]} {
5781 set kids $children($curview,$id)
5785 if {[llength $kids] == 1} {
5786 set child [lindex $kids 0]
5787 if {[info exists colormap($child)]
5788 && [llength $parents($curview,$child)] == 1} {
5789 set colormap($id) $colormap($child)
5795 foreach x [findcrossings $id] {
5797 # delimiter between corner crossings and other crossings
5798 if {[llength $badcolors] >= $ncolors - 1} break
5799 set origbad $badcolors
5801 if {[info exists colormap($x)]
5802 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5803 lappend badcolors $colormap($x)
5806 if {[llength $badcolors] >= $ncolors} {
5807 set badcolors $origbad
5809 set origbad $badcolors
5810 if {[llength $badcolors] < $ncolors - 1} {
5811 foreach child $kids {
5812 if {[info exists colormap($child)]
5813 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5814 lappend badcolors $colormap($child)
5816 foreach p $parents($curview,$child) {
5817 if {[info exists colormap($p)]
5818 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5819 lappend badcolors $colormap($p)
5823 if {[llength $badcolors] >= $ncolors} {
5824 set badcolors $origbad
5827 for {set i 0} {$i <= $ncolors} {incr i} {
5828 set c [lindex $colors $nextcolor]
5829 if {[incr nextcolor] >= $ncolors} {
5832 if {[lsearch -exact $badcolors $c]} break
5834 set colormap($id) $c
5837 proc bindline {t id} {
5840 $canv bind $t <Enter> "lineenter %x %y $id"
5841 $canv bind $t <Motion> "linemotion %x %y $id"
5842 $canv bind $t <Leave> "lineleave $id"
5843 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5846 proc drawtags {id x xt y1} {
5847 global idtags idheads idotherrefs mainhead
5848 global linespc lthickness
5849 global canv rowtextx curview fgcolor bgcolor ctxbut
5854 if {[info exists idtags($id)]} {
5855 set marks $idtags($id)
5856 set ntags [llength $marks]
5858 if {[info exists idheads($id)]} {
5859 set marks [concat $marks $idheads($id)]
5860 set nheads [llength $idheads($id)]
5862 if {[info exists idotherrefs($id)]} {
5863 set marks [concat $marks $idotherrefs($id)]
5869 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5870 set yt [expr {$y1 - 0.5 * $linespc}]
5871 set yb [expr {$yt + $linespc - 1}]
5875 foreach tag $marks {
5877 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5878 set wid [font measure mainfontbold $tag]
5880 set wid [font measure mainfont $tag]
5884 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5886 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5887 -width $lthickness -fill black -tags tag.$id]
5889 foreach tag $marks x $xvals wid $wvals {
5890 set xl [expr {$x + $delta}]
5891 set xr [expr {$x + $delta + $wid + $lthickness}]
5893 if {[incr ntags -1] >= 0} {
5895 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5896 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5897 -width 1 -outline black -fill yellow -tags tag.$id]
5898 $canv bind $t <1> [list showtag $tag 1]
5899 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5901 # draw a head or other ref
5902 if {[incr nheads -1] >= 0} {
5904 if {$tag eq $mainhead} {
5905 set font mainfontbold
5910 set xl [expr {$xl - $delta/2}]
5911 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5912 -width 1 -outline black -fill $col -tags tag.$id
5913 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5914 set rwid [font measure mainfont $remoteprefix]
5915 set xi [expr {$x + 1}]
5916 set yti [expr {$yt + 1}]
5917 set xri [expr {$x + $rwid}]
5918 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5919 -width 0 -fill "#ffddaa" -tags tag.$id
5922 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5923 -font $font -tags [list tag.$id text]]
5925 $canv bind $t <1> [list showtag $tag 1]
5926 } elseif {$nheads >= 0} {
5927 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
5933 proc xcoord {i level ln} {
5934 global canvx0 xspc1 xspc2
5936 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5937 if {$i > 0 && $i == $level} {
5938 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5939 } elseif {$i > $level} {
5940 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5945 proc show_status {msg} {
5949 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5950 -tags text -fill $fgcolor
5953 # Don't change the text pane cursor if it is currently the hand cursor,
5954 # showing that we are over a sha1 ID link.
5955 proc settextcursor {c} {
5956 global ctext curtextcursor
5958 if {[$ctext cget -cursor] == $curtextcursor} {
5959 $ctext config -cursor $c
5961 set curtextcursor $c
5964 proc nowbusy {what {name {}}} {
5965 global isbusy busyname statusw
5967 if {[array names isbusy] eq {}} {
5968 . config -cursor watch
5972 set busyname($what) $name
5974 $statusw conf -text $name
5978 proc notbusy {what} {
5979 global isbusy maincursor textcursor busyname statusw
5983 if {$busyname($what) ne {} &&
5984 [$statusw cget -text] eq $busyname($what)} {
5985 $statusw conf -text {}
5988 if {[array names isbusy] eq {}} {
5989 . config -cursor $maincursor
5990 settextcursor $textcursor
5994 proc findmatches {f} {
5995 global findtype findstring
5996 if {$findtype == [mc "Regexp"]} {
5997 set matches [regexp -indices -all -inline $findstring $f]
6000 if {$findtype == [mc "IgnCase"]} {
6001 set f [string tolower $f]
6002 set fs [string tolower $fs]
6006 set l [string length $fs]
6007 while {[set j [string first $fs $f $i]] >= 0} {
6008 lappend matches [list $j [expr {$j+$l-1}]]
6009 set i [expr {$j + $l}]
6015 proc dofind {{dirn 1} {wrap 1}} {
6016 global findstring findstartline findcurline selectedline numcommits
6017 global gdttype filehighlight fh_serial find_dirn findallowwrap
6019 if {[info exists find_dirn]} {
6020 if {$find_dirn == $dirn} return
6024 if {$findstring eq {} || $numcommits == 0} return
6025 if {$selectedline eq {}} {
6026 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
6028 set findstartline $selectedline
6030 set findcurline $findstartline
6031 nowbusy finding [mc "Searching"]
6032 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
6033 after cancel do_file_hl $fh_serial
6034 do_file_hl $fh_serial
6037 set findallowwrap $wrap
6041 proc stopfinding {} {
6042 global find_dirn findcurline fprogcoord
6044 if {[info exists find_dirn]} {
6055 global commitdata commitinfo numcommits findpattern findloc
6056 global findstartline findcurline findallowwrap
6057 global find_dirn gdttype fhighlights fprogcoord
6058 global curview varcorder vrownum varccommits vrowmod
6060 if {![info exists find_dirn]} {
6063 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
6066 if {$find_dirn > 0} {
6068 if {$l >= $numcommits} {
6071 if {$l <= $findstartline} {
6072 set lim [expr {$findstartline + 1}]
6075 set moretodo $findallowwrap
6082 if {$l >= $findstartline} {
6083 set lim [expr {$findstartline - 1}]
6086 set moretodo $findallowwrap
6089 set n [expr {($lim - $l) * $find_dirn}]
6094 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6095 update_arcrows $curview
6099 set ai [bsearch $vrownum($curview) $l]
6100 set a [lindex $varcorder($curview) $ai]
6101 set arow [lindex $vrownum($curview) $ai]
6102 set ids [lindex $varccommits($curview,$a)]
6103 set arowend [expr {$arow + [llength $ids]}]
6104 if {$gdttype eq [mc "containing:"]} {
6105 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6106 if {$l < $arow || $l >= $arowend} {
6108 set a [lindex $varcorder($curview) $ai]
6109 set arow [lindex $vrownum($curview) $ai]
6110 set ids [lindex $varccommits($curview,$a)]
6111 set arowend [expr {$arow + [llength $ids]}]
6113 set id [lindex $ids [expr {$l - $arow}]]
6114 # shouldn't happen unless git log doesn't give all the commits...
6115 if {![info exists commitdata($id)] ||
6116 ![doesmatch $commitdata($id)]} {
6119 if {![info exists commitinfo($id)]} {
6122 set info $commitinfo($id)
6123 foreach f $info ty $fldtypes {
6124 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6133 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6134 if {$l < $arow || $l >= $arowend} {
6136 set a [lindex $varcorder($curview) $ai]
6137 set arow [lindex $vrownum($curview) $ai]
6138 set ids [lindex $varccommits($curview,$a)]
6139 set arowend [expr {$arow + [llength $ids]}]
6141 set id [lindex $ids [expr {$l - $arow}]]
6142 if {![info exists fhighlights($id)]} {
6143 # this sets fhighlights($id) to -1
6144 askfilehighlight $l $id
6146 if {$fhighlights($id) > 0} {
6150 if {$fhighlights($id) < 0} {
6153 set findcurline [expr {$l - $find_dirn}]
6158 if {$found || ($domore && !$moretodo)} {
6174 set findcurline [expr {$l - $find_dirn}]
6176 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6180 set fprogcoord [expr {$n * 1.0 / $numcommits}]
6185 proc findselectline {l} {
6186 global findloc commentend ctext findcurline markingmatches gdttype
6188 set markingmatches 1
6191 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
6192 # highlight the matches in the comments
6193 set f [$ctext get 1.0 $commentend]
6194 set matches [findmatches $f]
6195 foreach match $matches {
6196 set start [lindex $match 0]
6197 set end [expr {[lindex $match 1] + 1}]
6198 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6204 # mark the bits of a headline or author that match a find string
6205 proc markmatches {canv l str tag matches font row} {
6208 set bbox [$canv bbox $tag]
6209 set x0 [lindex $bbox 0]
6210 set y0 [lindex $bbox 1]
6211 set y1 [lindex $bbox 3]
6212 foreach match $matches {
6213 set start [lindex $match 0]
6214 set end [lindex $match 1]
6215 if {$start > $end} continue
6216 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6217 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6218 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6219 [expr {$x0+$xlen+2}] $y1 \
6220 -outline {} -tags [list match$l matches] -fill yellow]
6222 if {$row == $selectedline} {
6223 $canv raise $t secsel
6228 proc unmarkmatches {} {
6229 global markingmatches
6231 allcanvs delete matches
6232 set markingmatches 0
6236 proc selcanvline {w x y} {
6237 global canv canvy0 ctext linespc
6239 set ymax [lindex [$canv cget -scrollregion] 3]
6240 if {$ymax == {}} return
6241 set yfrac [lindex [$canv yview] 0]
6242 set y [expr {$y + $yfrac * $ymax}]
6243 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6248 set xmax [lindex [$canv cget -scrollregion] 2]
6249 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6250 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6256 proc commit_descriptor {p} {
6258 if {![info exists commitinfo($p)]} {
6262 if {[llength $commitinfo($p)] > 1} {
6263 set l [lindex $commitinfo($p) 0]
6268 # append some text to the ctext widget, and make any SHA1 ID
6269 # that we know about be a clickable link.
6270 proc appendwithlinks {text tags} {
6271 global ctext linknum curview
6273 set start [$ctext index "end - 1c"]
6274 $ctext insert end $text $tags
6275 set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6279 set linkid [string range $text $s $e]
6281 $ctext tag delete link$linknum
6282 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6283 setlink $linkid link$linknum
6288 proc setlink {id lk} {
6289 global curview ctext pendinglinks
6292 if {[string length $id] < 40} {
6293 set matches [longid $id]
6294 if {[llength $matches] > 0} {
6295 if {[llength $matches] > 1} return
6297 set id [lindex $matches 0]
6300 set known [commitinview $id $curview]
6303 $ctext tag conf $lk -foreground blue -underline 1
6304 $ctext tag bind $lk <1> [list selbyid $id]
6305 $ctext tag bind $lk <Enter> {linkcursor %W 1}
6306 $ctext tag bind $lk <Leave> {linkcursor %W -1}
6308 lappend pendinglinks($id) $lk
6309 interestedin $id {makelink %P}
6313 proc makelink {id} {
6316 if {![info exists pendinglinks($id)]} return
6317 foreach lk $pendinglinks($id) {
6320 unset pendinglinks($id)
6323 proc linkcursor {w inc} {
6324 global linkentercount curtextcursor
6326 if {[incr linkentercount $inc] > 0} {
6327 $w configure -cursor hand2
6329 $w configure -cursor $curtextcursor
6330 if {$linkentercount < 0} {
6331 set linkentercount 0
6336 proc viewnextline {dir} {
6340 set ymax [lindex [$canv cget -scrollregion] 3]
6341 set wnow [$canv yview]
6342 set wtop [expr {[lindex $wnow 0] * $ymax}]
6343 set newtop [expr {$wtop + $dir * $linespc}]
6346 } elseif {$newtop > $ymax} {
6349 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6352 # add a list of tag or branch names at position pos
6353 # returns the number of names inserted
6354 proc appendrefs {pos ids var} {
6355 global ctext linknum curview $var maxrefs
6357 if {[catch {$ctext index $pos}]} {
6360 $ctext conf -state normal
6361 $ctext delete $pos "$pos lineend"
6364 foreach tag [set $var\($id\)] {
6365 lappend tags [list $tag $id]
6368 if {[llength $tags] > $maxrefs} {
6369 $ctext insert $pos "many ([llength $tags])"
6371 set tags [lsort -index 0 -decreasing $tags]
6374 set id [lindex $ti 1]
6377 $ctext tag delete $lk
6378 $ctext insert $pos $sep
6379 $ctext insert $pos [lindex $ti 0] $lk
6384 $ctext conf -state disabled
6385 return [llength $tags]
6388 # called when we have finished computing the nearby tags
6389 proc dispneartags {delay} {
6390 global selectedline currentid showneartags tagphase
6392 if {$selectedline eq {} || !$showneartags} return
6393 after cancel dispnexttag
6395 after 200 dispnexttag
6398 after idle dispnexttag
6403 proc dispnexttag {} {
6404 global selectedline currentid showneartags tagphase ctext
6406 if {$selectedline eq {} || !$showneartags} return
6407 switch -- $tagphase {
6409 set dtags [desctags $currentid]
6411 appendrefs precedes $dtags idtags
6415 set atags [anctags $currentid]
6417 appendrefs follows $atags idtags
6421 set dheads [descheads $currentid]
6422 if {$dheads ne {}} {
6423 if {[appendrefs branch $dheads idheads] > 1
6424 && [$ctext get "branch -3c"] eq "h"} {
6425 # turn "Branch" into "Branches"
6426 $ctext conf -state normal
6427 $ctext insert "branch -2c" "es"
6428 $ctext conf -state disabled
6433 if {[incr tagphase] <= 2} {
6434 after idle dispnexttag
6438 proc make_secsel {l} {
6439 global linehtag linentag linedtag canv canv2 canv3
6441 if {![info exists linehtag($l)]} return
6443 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
6444 -tags secsel -fill [$canv cget -selectbackground]]
6446 $canv2 delete secsel
6447 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
6448 -tags secsel -fill [$canv2 cget -selectbackground]]
6450 $canv3 delete secsel
6451 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
6452 -tags secsel -fill [$canv3 cget -selectbackground]]
6456 proc selectline {l isnew {desired_loc {}}} {
6457 global canv ctext commitinfo selectedline
6458 global canvy0 linespc parents children curview
6459 global currentid sha1entry
6460 global commentend idtags linknum
6461 global mergemax numcommits pending_select
6462 global cmitmode showneartags allcommits
6463 global targetrow targetid lastscrollrows
6464 global autoselect jump_to_here
6466 catch {unset pending_select}
6471 if {$l < 0 || $l >= $numcommits} return
6472 set id [commitonrow $l]
6477 if {$lastscrollrows < $numcommits} {
6481 set y [expr {$canvy0 + $l * $linespc}]
6482 set ymax [lindex [$canv cget -scrollregion] 3]
6483 set ytop [expr {$y - $linespc - 1}]
6484 set ybot [expr {$y + $linespc + 1}]
6485 set wnow [$canv yview]
6486 set wtop [expr {[lindex $wnow 0] * $ymax}]
6487 set wbot [expr {[lindex $wnow 1] * $ymax}]
6488 set wh [expr {$wbot - $wtop}]
6490 if {$ytop < $wtop} {
6491 if {$ybot < $wtop} {
6492 set newtop [expr {$y - $wh / 2.0}]
6495 if {$newtop > $wtop - $linespc} {
6496 set newtop [expr {$wtop - $linespc}]
6499 } elseif {$ybot > $wbot} {
6500 if {$ytop > $wbot} {
6501 set newtop [expr {$y - $wh / 2.0}]
6503 set newtop [expr {$ybot - $wh}]
6504 if {$newtop < $wtop + $linespc} {
6505 set newtop [expr {$wtop + $linespc}]
6509 if {$newtop != $wtop} {
6513 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6520 addtohistory [list selbyid $id]
6523 $sha1entry delete 0 end
6524 $sha1entry insert 0 $id
6526 $sha1entry selection from 0
6527 $sha1entry selection to end
6531 $ctext conf -state normal
6534 if {![info exists commitinfo($id)]} {
6537 set info $commitinfo($id)
6538 set date [formatdate [lindex $info 2]]
6539 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
6540 set date [formatdate [lindex $info 4]]
6541 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
6542 if {[info exists idtags($id)]} {
6543 $ctext insert end [mc "Tags:"]
6544 foreach tag $idtags($id) {
6545 $ctext insert end " $tag"
6547 $ctext insert end "\n"
6551 set olds $parents($curview,$id)
6552 if {[llength $olds] > 1} {
6555 if {$np >= $mergemax} {
6560 $ctext insert end "[mc "Parent"]: " $tag
6561 appendwithlinks [commit_descriptor $p] {}
6566 append headers "[mc "Parent"]: [commit_descriptor $p]"
6570 foreach c $children($curview,$id) {
6571 append headers "[mc "Child"]: [commit_descriptor $c]"
6574 # make anything that looks like a SHA1 ID be a clickable link
6575 appendwithlinks $headers {}
6576 if {$showneartags} {
6577 if {![info exists allcommits]} {
6580 $ctext insert end "[mc "Branch"]: "
6581 $ctext mark set branch "end -1c"
6582 $ctext mark gravity branch left
6583 $ctext insert end "\n[mc "Follows"]: "
6584 $ctext mark set follows "end -1c"
6585 $ctext mark gravity follows left
6586 $ctext insert end "\n[mc "Precedes"]: "
6587 $ctext mark set precedes "end -1c"
6588 $ctext mark gravity precedes left
6589 $ctext insert end "\n"
6592 $ctext insert end "\n"
6593 set comment [lindex $info 5]
6594 if {[string first "\r" $comment] >= 0} {
6595 set comment [string map {"\r" "\n "} $comment]
6597 appendwithlinks $comment {comment}
6599 $ctext tag remove found 1.0 end
6600 $ctext conf -state disabled
6601 set commentend [$ctext index "end - 1c"]
6603 set jump_to_here $desired_loc
6604 init_flist [mc "Comments"]
6605 if {$cmitmode eq "tree"} {
6607 } elseif {[llength $olds] <= 1} {
6614 proc selfirstline {} {
6619 proc sellastline {} {
6622 set l [expr {$numcommits - 1}]
6626 proc selnextline {dir} {
6629 if {$selectedline eq {}} return
6630 set l [expr {$selectedline + $dir}]
6635 proc selnextpage {dir} {
6636 global canv linespc selectedline numcommits
6638 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6642 allcanvs yview scroll [expr {$dir * $lpp}] units
6644 if {$selectedline eq {}} return
6645 set l [expr {$selectedline + $dir * $lpp}]
6648 } elseif {$l >= $numcommits} {
6649 set l [expr $numcommits - 1]
6655 proc unselectline {} {
6656 global selectedline currentid
6659 catch {unset currentid}
6660 allcanvs delete secsel
6664 proc reselectline {} {
6667 if {$selectedline ne {}} {
6668 selectline $selectedline 0
6672 proc addtohistory {cmd} {
6673 global history historyindex curview
6675 set elt [list $curview $cmd]
6676 if {$historyindex > 0
6677 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6681 if {$historyindex < [llength $history]} {
6682 set history [lreplace $history $historyindex end $elt]
6684 lappend history $elt
6687 if {$historyindex > 1} {
6688 .tf.bar.leftbut conf -state normal
6690 .tf.bar.leftbut conf -state disabled
6692 .tf.bar.rightbut conf -state disabled
6698 set view [lindex $elt 0]
6699 set cmd [lindex $elt 1]
6700 if {$curview != $view} {
6707 global history historyindex
6710 if {$historyindex > 1} {
6711 incr historyindex -1
6712 godo [lindex $history [expr {$historyindex - 1}]]
6713 .tf.bar.rightbut conf -state normal
6715 if {$historyindex <= 1} {
6716 .tf.bar.leftbut conf -state disabled
6721 global history historyindex
6724 if {$historyindex < [llength $history]} {
6725 set cmd [lindex $history $historyindex]
6728 .tf.bar.leftbut conf -state normal
6730 if {$historyindex >= [llength $history]} {
6731 .tf.bar.rightbut conf -state disabled
6736 global treefilelist treeidlist diffids diffmergeid treepending
6737 global nullid nullid2
6740 catch {unset diffmergeid}
6741 if {![info exists treefilelist($id)]} {
6742 if {![info exists treepending]} {
6743 if {$id eq $nullid} {
6744 set cmd [list | git ls-files]
6745 } elseif {$id eq $nullid2} {
6746 set cmd [list | git ls-files --stage -t]
6748 set cmd [list | git ls-tree -r $id]
6750 if {[catch {set gtf [open $cmd r]}]} {
6754 set treefilelist($id) {}
6755 set treeidlist($id) {}
6756 fconfigure $gtf -blocking 0 -encoding binary
6757 filerun $gtf [list gettreeline $gtf $id]
6764 proc gettreeline {gtf id} {
6765 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6768 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6769 if {$diffids eq $nullid} {
6772 set i [string first "\t" $line]
6773 if {$i < 0} continue
6774 set fname [string range $line [expr {$i+1}] end]
6775 set line [string range $line 0 [expr {$i-1}]]
6776 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6777 set sha1 [lindex $line 2]
6778 lappend treeidlist($id) $sha1
6780 if {[string index $fname 0] eq "\""} {
6781 set fname [lindex $fname 0]
6783 set fname [encoding convertfrom $fname]
6784 lappend treefilelist($id) $fname
6787 return [expr {$nl >= 1000? 2: 1}]
6791 if {$cmitmode ne "tree"} {
6792 if {![info exists diffmergeid]} {
6793 gettreediffs $diffids
6795 } elseif {$id ne $diffids} {
6804 global treefilelist treeidlist diffids nullid nullid2
6805 global ctext_file_names ctext_file_lines
6806 global ctext commentend
6808 set i [lsearch -exact $treefilelist($diffids) $f]
6810 puts "oops, $f not in list for id $diffids"
6813 if {$diffids eq $nullid} {
6814 if {[catch {set bf [open $f r]} err]} {
6815 puts "oops, can't read $f: $err"
6819 set blob [lindex $treeidlist($diffids) $i]
6820 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6821 puts "oops, error reading blob $blob: $err"
6825 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
6826 filerun $bf [list getblobline $bf $diffids]
6827 $ctext config -state normal
6828 clear_ctext $commentend
6829 lappend ctext_file_names $f
6830 lappend ctext_file_lines [lindex [split $commentend "."] 0]
6831 $ctext insert end "\n"
6832 $ctext insert end "$f\n" filesep
6833 $ctext config -state disabled
6834 $ctext yview $commentend
6838 proc getblobline {bf id} {
6839 global diffids cmitmode ctext
6841 if {$id ne $diffids || $cmitmode ne "tree"} {
6845 $ctext config -state normal
6847 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6848 $ctext insert end "$line\n"
6851 global jump_to_here ctext_file_names commentend
6853 # delete last newline
6854 $ctext delete "end - 2c" "end - 1c"
6856 if {$jump_to_here ne {} &&
6857 [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
6858 set lnum [expr {[lindex $jump_to_here 1] +
6859 [lindex [split $commentend .] 0]}]
6860 mark_ctext_line $lnum
6864 $ctext config -state disabled
6865 return [expr {$nl >= 1000? 2: 1}]
6868 proc mark_ctext_line {lnum} {
6869 global ctext markbgcolor
6871 $ctext tag delete omark
6872 $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
6873 $ctext tag conf omark -background $markbgcolor
6877 proc mergediff {id} {
6879 global diffids treediffs
6880 global parents curview
6884 set treediffs($id) {}
6885 set np [llength $parents($curview,$id)]
6890 proc startdiff {ids} {
6891 global treediffs diffids treepending diffmergeid nullid nullid2
6895 catch {unset diffmergeid}
6896 if {![info exists treediffs($ids)] ||
6897 [lsearch -exact $ids $nullid] >= 0 ||
6898 [lsearch -exact $ids $nullid2] >= 0} {
6899 if {![info exists treepending]} {
6907 proc path_filter {filter name} {
6909 set l [string length $p]
6910 if {[string index $p end] eq "/"} {
6911 if {[string compare -length $l $p $name] == 0} {
6915 if {[string compare -length $l $p $name] == 0 &&
6916 ([string length $name] == $l ||
6917 [string index $name $l] eq "/")} {
6925 proc addtocflist {ids} {
6928 add_flist $treediffs($ids)
6932 proc diffcmd {ids flags} {
6933 global nullid nullid2
6935 set i [lsearch -exact $ids $nullid]
6936 set j [lsearch -exact $ids $nullid2]
6938 if {[llength $ids] > 1 && $j < 0} {
6939 # comparing working directory with some specific revision
6940 set cmd [concat | git diff-index $flags]
6942 lappend cmd -R [lindex $ids 1]
6944 lappend cmd [lindex $ids 0]
6947 # comparing working directory with index
6948 set cmd [concat | git diff-files $flags]
6953 } elseif {$j >= 0} {
6954 set cmd [concat | git diff-index --cached $flags]
6955 if {[llength $ids] > 1} {
6956 # comparing index with specific revision
6958 lappend cmd -R [lindex $ids 1]
6960 lappend cmd [lindex $ids 0]
6963 # comparing index with HEAD
6967 set cmd [concat | git diff-tree -r $flags $ids]
6972 proc gettreediffs {ids} {
6973 global treediff treepending
6975 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6977 set treepending $ids
6979 fconfigure $gdtf -blocking 0 -encoding binary
6980 filerun $gdtf [list gettreediffline $gdtf $ids]
6983 proc gettreediffline {gdtf ids} {
6984 global treediff treediffs treepending diffids diffmergeid
6985 global cmitmode vfilelimit curview limitdiffs perfile_attrs
6990 if {$perfile_attrs} {
6991 # cache_gitattr is slow, and even slower on win32 where we
6992 # have to invoke it for only about 30 paths at a time
6994 if {[tk windowingsystem] == "win32"} {
6998 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
6999 set i [string first "\t" $line]
7001 set file [string range $line [expr {$i+1}] end]
7002 if {[string index $file 0] eq "\""} {
7003 set file [lindex $file 0]
7005 set file [encoding convertfrom $file]
7006 if {$file ne [lindex $treediff end]} {
7007 lappend treediff $file
7008 lappend sublist $file
7012 if {$perfile_attrs} {
7013 cache_gitattr encoding $sublist
7016 return [expr {$nr >= $max? 2: 1}]
7019 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7021 foreach f $treediff {
7022 if {[path_filter $vfilelimit($curview) $f]} {
7026 set treediffs($ids) $flist
7028 set treediffs($ids) $treediff
7031 if {$cmitmode eq "tree"} {
7033 } elseif {$ids != $diffids} {
7034 if {![info exists diffmergeid]} {
7035 gettreediffs $diffids
7043 # empty string or positive integer
7044 proc diffcontextvalidate {v} {
7045 return [regexp {^(|[1-9][0-9]*)$} $v]
7048 proc diffcontextchange {n1 n2 op} {
7049 global diffcontextstring diffcontext
7051 if {[string is integer -strict $diffcontextstring]} {
7052 if {$diffcontextstring > 0} {
7053 set diffcontext $diffcontextstring
7059 proc changeignorespace {} {
7063 proc getblobdiffs {ids} {
7064 global blobdifffd diffids env
7065 global diffinhdr treediffs
7068 global limitdiffs vfilelimit curview
7069 global diffencoding targetline diffnparents
7071 set cmd [diffcmd $ids "-p -C --cc --no-commit-id -U$diffcontext"]
7075 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7076 set cmd [concat $cmd -- $vfilelimit($curview)]
7078 if {[catch {set bdf [open $cmd r]} err]} {
7079 error_popup [mc "Error getting diffs: %s" $err]
7085 set diffencoding [get_path_encoding {}]
7086 fconfigure $bdf -blocking 0 -encoding binary
7087 set blobdifffd($ids) $bdf
7088 filerun $bdf [list getblobdiffline $bdf $diffids]
7091 proc setinlist {var i val} {
7094 while {[llength [set $var]] < $i} {
7097 if {[llength [set $var]] == $i} {
7104 proc makediffhdr {fname ids} {
7105 global ctext curdiffstart treediffs diffencoding
7106 global ctext_file_names jump_to_here targetline diffline
7108 set fname [encoding convertfrom $fname]
7109 set diffencoding [get_path_encoding $fname]
7110 set i [lsearch -exact $treediffs($ids) $fname]
7112 setinlist difffilestart $i $curdiffstart
7114 lset ctext_file_names end $fname
7115 set l [expr {(78 - [string length $fname]) / 2}]
7116 set pad [string range "----------------------------------------" 1 $l]
7117 $ctext insert $curdiffstart "$pad $fname $pad" filesep
7119 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7120 set targetline [lindex $jump_to_here 1]
7125 proc getblobdiffline {bdf ids} {
7126 global diffids blobdifffd ctext curdiffstart
7127 global diffnexthead diffnextnote difffilestart
7128 global ctext_file_names ctext_file_lines
7129 global diffinhdr treediffs mergemax diffnparents
7130 global diffencoding jump_to_here targetline diffline
7133 $ctext conf -state normal
7134 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7135 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7139 if {![string compare -length 5 "diff " $line]} {
7140 if {![regexp {^diff (--cc|--git) } $line m type]} {
7141 set line [encoding convertfrom $line]
7142 $ctext insert end "$line\n" hunksep
7145 # start of a new file
7147 $ctext insert end "\n"
7148 set curdiffstart [$ctext index "end - 1c"]
7149 lappend ctext_file_names ""
7150 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7151 $ctext insert end "\n" filesep
7153 if {$type eq "--cc"} {
7154 # start of a new file in a merge diff
7155 set fname [string range $line 10 end]
7156 if {[lsearch -exact $treediffs($ids) $fname] < 0} {
7157 lappend treediffs($ids) $fname
7158 add_flist [list $fname]
7162 set line [string range $line 11 end]
7163 # If the name hasn't changed the length will be odd,
7164 # the middle char will be a space, and the two bits either
7165 # side will be a/name and b/name, or "a/name" and "b/name".
7166 # If the name has changed we'll get "rename from" and
7167 # "rename to" or "copy from" and "copy to" lines following
7168 # this, and we'll use them to get the filenames.
7169 # This complexity is necessary because spaces in the
7170 # filename(s) don't get escaped.
7171 set l [string length $line]
7172 set i [expr {$l / 2}]
7173 if {!(($l & 1) && [string index $line $i] eq " " &&
7174 [string range $line 2 [expr {$i - 1}]] eq \
7175 [string range $line [expr {$i + 3}] end])} {
7178 # unescape if quoted and chop off the a/ from the front
7179 if {[string index $line 0] eq "\""} {
7180 set fname [string range [lindex $line 0] 2 end]
7182 set fname [string range $line 2 [expr {$i - 1}]]
7185 makediffhdr $fname $ids
7187 } elseif {![string compare -length 16 "* Unmerged path " $line]} {
7188 set fname [encoding convertfrom [string range $line 16 end]]
7189 $ctext insert end "\n"
7190 set curdiffstart [$ctext index "end - 1c"]
7191 lappend ctext_file_names $fname
7192 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7193 $ctext insert end "$line\n" filesep
7194 set i [lsearch -exact $treediffs($ids) $fname]
7196 setinlist difffilestart $i $curdiffstart
7199 } elseif {![string compare -length 2 "@@" $line]} {
7200 regexp {^@@+} $line ats
7201 set line [encoding convertfrom $diffencoding $line]
7202 $ctext insert end "$line\n" hunksep
7203 if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
7206 set diffnparents [expr {[string length $ats] - 1}]
7209 } elseif {$diffinhdr} {
7210 if {![string compare -length 12 "rename from " $line]} {
7211 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7212 if {[string index $fname 0] eq "\""} {
7213 set fname [lindex $fname 0]
7215 set fname [encoding convertfrom $fname]
7216 set i [lsearch -exact $treediffs($ids) $fname]
7218 setinlist difffilestart $i $curdiffstart
7220 } elseif {![string compare -length 10 $line "rename to "] ||
7221 ![string compare -length 8 $line "copy to "]} {
7222 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7223 if {[string index $fname 0] eq "\""} {
7224 set fname [lindex $fname 0]
7226 makediffhdr $fname $ids
7227 } elseif {[string compare -length 3 $line "---"] == 0} {
7230 } elseif {[string compare -length 3 $line "+++"] == 0} {
7234 $ctext insert end "$line\n" filesep
7237 set line [encoding convertfrom $diffencoding $line]
7238 # parse the prefix - one ' ', '-' or '+' for each parent
7239 set prefix [string range $line 0 [expr {$diffnparents - 1}]]
7240 set tag [expr {$diffnparents > 1? "m": "d"}]
7241 if {[string trim $prefix " -+"] eq {}} {
7242 # prefix only has " ", "-" and "+" in it: normal diff line
7243 set num [string first "-" $prefix]
7245 # removed line, first parent with line is $num
7246 if {$num >= $mergemax} {
7249 $ctext insert end "$line\n" $tag$num
7252 if {[string first "+" $prefix] >= 0} {
7254 lappend tags ${tag}result
7255 if {$diffnparents > 1} {
7256 set num [string first " " $prefix]
7258 if {$num >= $mergemax} {
7265 if {$targetline ne {}} {
7266 if {$diffline == $targetline} {
7267 set seehere [$ctext index "end - 1 chars"]
7273 $ctext insert end "$line\n" $tags
7276 # "\ No newline at end of file",
7277 # or something else we don't recognize
7278 $ctext insert end "$line\n" hunksep
7282 if {[info exists seehere]} {
7283 mark_ctext_line [lindex [split $seehere .] 0]
7285 $ctext conf -state disabled
7290 return [expr {$nr >= 1000? 2: 1}]
7293 proc changediffdisp {} {
7294 global ctext diffelide
7296 $ctext tag conf d0 -elide [lindex $diffelide 0]
7297 $ctext tag conf dresult -elide [lindex $diffelide 1]
7300 proc highlightfile {loc cline} {
7301 global ctext cflist cflist_top
7304 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7305 $cflist tag add highlight $cline.0 "$cline.0 lineend"
7306 $cflist see $cline.0
7307 set cflist_top $cline
7311 global difffilestart ctext cmitmode
7313 if {$cmitmode eq "tree"} return
7316 set here [$ctext index @0,0]
7317 foreach loc $difffilestart {
7318 if {[$ctext compare $loc >= $here]} {
7319 highlightfile $prev $prevline
7325 highlightfile $prev $prevline
7329 global difffilestart ctext cmitmode
7331 if {$cmitmode eq "tree"} return
7332 set here [$ctext index @0,0]
7334 foreach loc $difffilestart {
7336 if {[$ctext compare $loc > $here]} {
7337 highlightfile $loc $line
7343 proc clear_ctext {{first 1.0}} {
7344 global ctext smarktop smarkbot
7345 global ctext_file_names ctext_file_lines
7348 set l [lindex [split $first .] 0]
7349 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
7352 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
7355 $ctext delete $first end
7356 if {$first eq "1.0"} {
7357 catch {unset pendinglinks}
7359 set ctext_file_names {}
7360 set ctext_file_lines {}
7363 proc settabs {{firstab {}}} {
7364 global firsttabstop tabstop ctext have_tk85
7366 if {$firstab ne {} && $have_tk85} {
7367 set firsttabstop $firstab
7369 set w [font measure textfont "0"]
7370 if {$firsttabstop != 0} {
7371 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
7372 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
7373 } elseif {$have_tk85 || $tabstop != 8} {
7374 $ctext conf -tabs [expr {$tabstop * $w}]
7376 $ctext conf -tabs {}
7380 proc incrsearch {name ix op} {
7381 global ctext searchstring searchdirn
7383 $ctext tag remove found 1.0 end
7384 if {[catch {$ctext index anchor}]} {
7385 # no anchor set, use start of selection, or of visible area
7386 set sel [$ctext tag ranges sel]
7388 $ctext mark set anchor [lindex $sel 0]
7389 } elseif {$searchdirn eq "-forwards"} {
7390 $ctext mark set anchor @0,0
7392 $ctext mark set anchor @0,[winfo height $ctext]
7395 if {$searchstring ne {}} {
7396 set here [$ctext search $searchdirn -- $searchstring anchor]
7405 global sstring ctext searchstring searchdirn
7408 $sstring icursor end
7409 set searchdirn -forwards
7410 if {$searchstring ne {}} {
7411 set sel [$ctext tag ranges sel]
7413 set start "[lindex $sel 0] + 1c"
7414 } elseif {[catch {set start [$ctext index anchor]}]} {
7417 set match [$ctext search -count mlen -- $searchstring $start]
7418 $ctext tag remove sel 1.0 end
7424 set mend "$match + $mlen c"
7425 $ctext tag add sel $match $mend
7426 $ctext mark unset anchor
7430 proc dosearchback {} {
7431 global sstring ctext searchstring searchdirn
7434 $sstring icursor end
7435 set searchdirn -backwards
7436 if {$searchstring ne {}} {
7437 set sel [$ctext tag ranges sel]
7439 set start [lindex $sel 0]
7440 } elseif {[catch {set start [$ctext index anchor]}]} {
7441 set start @0,[winfo height $ctext]
7443 set match [$ctext search -backwards -count ml -- $searchstring $start]
7444 $ctext tag remove sel 1.0 end
7450 set mend "$match + $ml c"
7451 $ctext tag add sel $match $mend
7452 $ctext mark unset anchor
7456 proc searchmark {first last} {
7457 global ctext searchstring
7461 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
7462 if {$match eq {}} break
7463 set mend "$match + $mlen c"
7464 $ctext tag add found $match $mend
7468 proc searchmarkvisible {doall} {
7469 global ctext smarktop smarkbot
7471 set topline [lindex [split [$ctext index @0,0] .] 0]
7472 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
7473 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
7474 # no overlap with previous
7475 searchmark $topline $botline
7476 set smarktop $topline
7477 set smarkbot $botline
7479 if {$topline < $smarktop} {
7480 searchmark $topline [expr {$smarktop-1}]
7481 set smarktop $topline
7483 if {$botline > $smarkbot} {
7484 searchmark [expr {$smarkbot+1}] $botline
7485 set smarkbot $botline
7490 proc scrolltext {f0 f1} {
7493 .bleft.bottom.sb set $f0 $f1
7494 if {$searchstring ne {}} {
7500 global linespc charspc canvx0 canvy0
7501 global xspc1 xspc2 lthickness
7503 set linespc [font metrics mainfont -linespace]
7504 set charspc [font measure mainfont "m"]
7505 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
7506 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
7507 set lthickness [expr {int($linespc / 9) + 1}]
7508 set xspc1(0) $linespc
7516 set ymax [lindex [$canv cget -scrollregion] 3]
7517 if {$ymax eq {} || $ymax == 0} return
7518 set span [$canv yview]
7521 allcanvs yview moveto [lindex $span 0]
7523 if {$selectedline ne {}} {
7524 selectline $selectedline 0
7525 allcanvs yview moveto [lindex $span 0]
7529 proc parsefont {f n} {
7532 set fontattr($f,family) [lindex $n 0]
7534 if {$s eq {} || $s == 0} {
7537 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
7539 set fontattr($f,size) $s
7540 set fontattr($f,weight) normal
7541 set fontattr($f,slant) roman
7542 foreach style [lrange $n 2 end] {
7545 "bold" {set fontattr($f,weight) $style}
7547 "italic" {set fontattr($f,slant) $style}
7552 proc fontflags {f {isbold 0}} {
7555 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
7556 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
7557 -slant $fontattr($f,slant)]
7563 set n [list $fontattr($f,family) $fontattr($f,size)]
7564 if {$fontattr($f,weight) eq "bold"} {
7567 if {$fontattr($f,slant) eq "italic"} {
7573 proc incrfont {inc} {
7574 global mainfont textfont ctext canv cflist showrefstop
7575 global stopped entries fontattr
7578 set s $fontattr(mainfont,size)
7583 set fontattr(mainfont,size) $s
7584 font config mainfont -size $s
7585 font config mainfontbold -size $s
7586 set mainfont [fontname mainfont]
7587 set s $fontattr(textfont,size)
7592 set fontattr(textfont,size) $s
7593 font config textfont -size $s
7594 font config textfontbold -size $s
7595 set textfont [fontname textfont]
7602 global sha1entry sha1string
7603 if {[string length $sha1string] == 40} {
7604 $sha1entry delete 0 end
7608 proc sha1change {n1 n2 op} {
7609 global sha1string currentid sha1but
7610 if {$sha1string == {}
7611 || ([info exists currentid] && $sha1string == $currentid)} {
7616 if {[$sha1but cget -state] == $state} return
7617 if {$state == "normal"} {
7618 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
7620 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
7624 proc gotocommit {} {
7625 global sha1string tagids headids curview varcid
7627 if {$sha1string == {}
7628 || ([info exists currentid] && $sha1string == $currentid)} return
7629 if {[info exists tagids($sha1string)]} {
7630 set id $tagids($sha1string)
7631 } elseif {[info exists headids($sha1string)]} {
7632 set id $headids($sha1string)
7634 set id [string tolower $sha1string]
7635 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7636 set matches [longid $id]
7637 if {$matches ne {}} {
7638 if {[llength $matches] > 1} {
7639 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7642 set id [lindex $matches 0]
7646 if {[commitinview $id $curview]} {
7647 selectline [rowofcommit $id] 1
7650 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7651 set msg [mc "SHA1 id %s is not known" $sha1string]
7653 set msg [mc "Tag/Head %s is not known" $sha1string]
7658 proc lineenter {x y id} {
7659 global hoverx hovery hoverid hovertimer
7660 global commitinfo canv
7662 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7666 if {[info exists hovertimer]} {
7667 after cancel $hovertimer
7669 set hovertimer [after 500 linehover]
7673 proc linemotion {x y id} {
7674 global hoverx hovery hoverid hovertimer
7676 if {[info exists hoverid] && $id == $hoverid} {
7679 if {[info exists hovertimer]} {
7680 after cancel $hovertimer
7682 set hovertimer [after 500 linehover]
7686 proc lineleave {id} {
7687 global hoverid hovertimer canv
7689 if {[info exists hoverid] && $id == $hoverid} {
7691 if {[info exists hovertimer]} {
7692 after cancel $hovertimer
7700 global hoverx hovery hoverid hovertimer
7701 global canv linespc lthickness
7704 set text [lindex $commitinfo($hoverid) 0]
7705 set ymax [lindex [$canv cget -scrollregion] 3]
7706 if {$ymax == {}} return
7707 set yfrac [lindex [$canv yview] 0]
7708 set x [expr {$hoverx + 2 * $linespc}]
7709 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7710 set x0 [expr {$x - 2 * $lthickness}]
7711 set y0 [expr {$y - 2 * $lthickness}]
7712 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7713 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7714 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7715 -fill \#ffff80 -outline black -width 1 -tags hover]
7717 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7722 proc clickisonarrow {id y} {
7725 set ranges [rowranges $id]
7726 set thresh [expr {2 * $lthickness + 6}]
7727 set n [expr {[llength $ranges] - 1}]
7728 for {set i 1} {$i < $n} {incr i} {
7729 set row [lindex $ranges $i]
7730 if {abs([yc $row] - $y) < $thresh} {
7737 proc arrowjump {id n y} {
7740 # 1 <-> 2, 3 <-> 4, etc...
7741 set n [expr {(($n - 1) ^ 1) + 1}]
7742 set row [lindex [rowranges $id] $n]
7744 set ymax [lindex [$canv cget -scrollregion] 3]
7745 if {$ymax eq {} || $ymax <= 0} return
7746 set view [$canv yview]
7747 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7748 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7752 allcanvs yview moveto $yfrac
7755 proc lineclick {x y id isnew} {
7756 global ctext commitinfo children canv thickerline curview
7758 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7763 # draw this line thicker than normal
7767 set ymax [lindex [$canv cget -scrollregion] 3]
7768 if {$ymax eq {}} return
7769 set yfrac [lindex [$canv yview] 0]
7770 set y [expr {$y + $yfrac * $ymax}]
7772 set dirn [clickisonarrow $id $y]
7774 arrowjump $id $dirn $y
7779 addtohistory [list lineclick $x $y $id 0]
7781 # fill the details pane with info about this line
7782 $ctext conf -state normal
7785 $ctext insert end "[mc "Parent"]:\t"
7786 $ctext insert end $id link0
7788 set info $commitinfo($id)
7789 $ctext insert end "\n\t[lindex $info 0]\n"
7790 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7791 set date [formatdate [lindex $info 2]]
7792 $ctext insert end "\t[mc "Date"]:\t$date\n"
7793 set kids $children($curview,$id)
7795 $ctext insert end "\n[mc "Children"]:"
7797 foreach child $kids {
7799 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7800 set info $commitinfo($child)
7801 $ctext insert end "\n\t"
7802 $ctext insert end $child link$i
7803 setlink $child link$i
7804 $ctext insert end "\n\t[lindex $info 0]"
7805 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7806 set date [formatdate [lindex $info 2]]
7807 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7810 $ctext conf -state disabled
7814 proc normalline {} {
7816 if {[info exists thickerline]} {
7825 if {[commitinview $id $curview]} {
7826 selectline [rowofcommit $id] 1
7832 if {![info exists startmstime]} {
7833 set startmstime [clock clicks -milliseconds]
7835 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7838 proc rowmenu {x y id} {
7839 global rowctxmenu selectedline rowmenuid curview
7840 global nullid nullid2 fakerowmenu mainhead
7844 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7849 if {$id ne $nullid && $id ne $nullid2} {
7850 set menu $rowctxmenu
7851 if {$mainhead ne {}} {
7852 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7854 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7857 set menu $fakerowmenu
7859 $menu entryconfigure [mca "Diff this -> selected"] -state $state
7860 $menu entryconfigure [mca "Diff selected -> this"] -state $state
7861 $menu entryconfigure [mca "Make patch"] -state $state
7862 tk_popup $menu $x $y
7865 proc diffvssel {dirn} {
7866 global rowmenuid selectedline
7868 if {$selectedline eq {}} return
7870 set oldid [commitonrow $selectedline]
7871 set newid $rowmenuid
7873 set oldid $rowmenuid
7874 set newid [commitonrow $selectedline]
7876 addtohistory [list doseldiff $oldid $newid]
7877 doseldiff $oldid $newid
7880 proc doseldiff {oldid newid} {
7884 $ctext conf -state normal
7886 init_flist [mc "Top"]
7887 $ctext insert end "[mc "From"] "
7888 $ctext insert end $oldid link0
7889 setlink $oldid link0
7890 $ctext insert end "\n "
7891 $ctext insert end [lindex $commitinfo($oldid) 0]
7892 $ctext insert end "\n\n[mc "To"] "
7893 $ctext insert end $newid link1
7894 setlink $newid link1
7895 $ctext insert end "\n "
7896 $ctext insert end [lindex $commitinfo($newid) 0]
7897 $ctext insert end "\n"
7898 $ctext conf -state disabled
7899 $ctext tag remove found 1.0 end
7900 startdiff [list $oldid $newid]
7904 global rowmenuid currentid commitinfo patchtop patchnum
7906 if {![info exists currentid]} return
7907 set oldid $currentid
7908 set oldhead [lindex $commitinfo($oldid) 0]
7909 set newid $rowmenuid
7910 set newhead [lindex $commitinfo($newid) 0]
7913 catch {destroy $top}
7916 label $top.title -text [mc "Generate patch"]
7917 grid $top.title - -pady 10
7918 label $top.from -text [mc "From:"]
7919 entry $top.fromsha1 -width 40 -relief flat
7920 $top.fromsha1 insert 0 $oldid
7921 $top.fromsha1 conf -state readonly
7922 grid $top.from $top.fromsha1 -sticky w
7923 entry $top.fromhead -width 60 -relief flat
7924 $top.fromhead insert 0 $oldhead
7925 $top.fromhead conf -state readonly
7926 grid x $top.fromhead -sticky w
7927 label $top.to -text [mc "To:"]
7928 entry $top.tosha1 -width 40 -relief flat
7929 $top.tosha1 insert 0 $newid
7930 $top.tosha1 conf -state readonly
7931 grid $top.to $top.tosha1 -sticky w
7932 entry $top.tohead -width 60 -relief flat
7933 $top.tohead insert 0 $newhead
7934 $top.tohead conf -state readonly
7935 grid x $top.tohead -sticky w
7936 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7937 grid $top.rev x -pady 10
7938 label $top.flab -text [mc "Output file:"]
7939 entry $top.fname -width 60
7940 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7942 grid $top.flab $top.fname -sticky w
7944 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7945 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7946 bind $top <Key-Return> mkpatchgo
7947 bind $top <Key-Escape> mkpatchcan
7948 grid $top.buts.gen $top.buts.can
7949 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7950 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7951 grid $top.buts - -pady 10 -sticky ew
7955 proc mkpatchrev {} {
7958 set oldid [$patchtop.fromsha1 get]
7959 set oldhead [$patchtop.fromhead get]
7960 set newid [$patchtop.tosha1 get]
7961 set newhead [$patchtop.tohead get]
7962 foreach e [list fromsha1 fromhead tosha1 tohead] \
7963 v [list $newid $newhead $oldid $oldhead] {
7964 $patchtop.$e conf -state normal
7965 $patchtop.$e delete 0 end
7966 $patchtop.$e insert 0 $v
7967 $patchtop.$e conf -state readonly
7972 global patchtop nullid nullid2
7974 set oldid [$patchtop.fromsha1 get]
7975 set newid [$patchtop.tosha1 get]
7976 set fname [$patchtop.fname get]
7977 set cmd [diffcmd [list $oldid $newid] -p]
7978 # trim off the initial "|"
7979 set cmd [lrange $cmd 1 end]
7980 lappend cmd >$fname &
7981 if {[catch {eval exec $cmd} err]} {
7982 error_popup "[mc "Error creating patch:"] $err" $patchtop
7984 catch {destroy $patchtop}
7988 proc mkpatchcan {} {
7991 catch {destroy $patchtop}
7996 global rowmenuid mktagtop commitinfo
8000 catch {destroy $top}
8003 label $top.title -text [mc "Create tag"]
8004 grid $top.title - -pady 10
8005 label $top.id -text [mc "ID:"]
8006 entry $top.sha1 -width 40 -relief flat
8007 $top.sha1 insert 0 $rowmenuid
8008 $top.sha1 conf -state readonly
8009 grid $top.id $top.sha1 -sticky w
8010 entry $top.head -width 60 -relief flat
8011 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8012 $top.head conf -state readonly
8013 grid x $top.head -sticky w
8014 label $top.tlab -text [mc "Tag name:"]
8015 entry $top.tag -width 60
8016 grid $top.tlab $top.tag -sticky w
8018 button $top.buts.gen -text [mc "Create"] -command mktaggo
8019 button $top.buts.can -text [mc "Cancel"] -command mktagcan
8020 bind $top <Key-Return> mktaggo
8021 bind $top <Key-Escape> mktagcan
8022 grid $top.buts.gen $top.buts.can
8023 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8024 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8025 grid $top.buts - -pady 10 -sticky ew
8030 global mktagtop env tagids idtags
8032 set id [$mktagtop.sha1 get]
8033 set tag [$mktagtop.tag get]
8035 error_popup [mc "No tag name specified"] $mktagtop
8038 if {[info exists tagids($tag)]} {
8039 error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
8043 exec git tag $tag $id
8045 error_popup "[mc "Error creating tag:"] $err" $mktagtop
8049 set tagids($tag) $id
8050 lappend idtags($id) $tag
8058 proc redrawtags {id} {
8059 global canv linehtag idpos currentid curview cmitlisted
8060 global canvxmax iddrawn circleitem mainheadid circlecolors
8062 if {![commitinview $id $curview]} return
8063 if {![info exists iddrawn($id)]} return
8064 set row [rowofcommit $id]
8065 if {$id eq $mainheadid} {
8068 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
8070 $canv itemconf $circleitem($row) -fill $ofill
8071 $canv delete tag.$id
8072 set xt [eval drawtags $id $idpos($id)]
8073 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
8074 set text [$canv itemcget $linehtag($row) -text]
8075 set font [$canv itemcget $linehtag($row) -font]
8076 set xr [expr {$xt + [font measure $font $text]}]
8077 if {$xr > $canvxmax} {
8081 if {[info exists currentid] && $currentid == $id} {
8089 catch {destroy $mktagtop}
8094 if {![domktag]} return
8098 proc writecommit {} {
8099 global rowmenuid wrcomtop commitinfo wrcomcmd
8101 set top .writecommit
8103 catch {destroy $top}
8106 label $top.title -text [mc "Write commit to file"]
8107 grid $top.title - -pady 10
8108 label $top.id -text [mc "ID:"]
8109 entry $top.sha1 -width 40 -relief flat
8110 $top.sha1 insert 0 $rowmenuid
8111 $top.sha1 conf -state readonly
8112 grid $top.id $top.sha1 -sticky w
8113 entry $top.head -width 60 -relief flat
8114 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8115 $top.head conf -state readonly
8116 grid x $top.head -sticky w
8117 label $top.clab -text [mc "Command:"]
8118 entry $top.cmd -width 60 -textvariable wrcomcmd
8119 grid $top.clab $top.cmd -sticky w -pady 10
8120 label $top.flab -text [mc "Output file:"]
8121 entry $top.fname -width 60
8122 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
8123 grid $top.flab $top.fname -sticky w
8125 button $top.buts.gen -text [mc "Write"] -command wrcomgo
8126 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
8127 bind $top <Key-Return> wrcomgo
8128 bind $top <Key-Escape> wrcomcan
8129 grid $top.buts.gen $top.buts.can
8130 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8131 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8132 grid $top.buts - -pady 10 -sticky ew
8139 set id [$wrcomtop.sha1 get]
8140 set cmd "echo $id | [$wrcomtop.cmd get]"
8141 set fname [$wrcomtop.fname get]
8142 if {[catch {exec sh -c $cmd >$fname &} err]} {
8143 error_popup "[mc "Error writing commit:"] $err" $wrcomtop
8145 catch {destroy $wrcomtop}
8152 catch {destroy $wrcomtop}
8157 global rowmenuid mkbrtop
8160 catch {destroy $top}
8163 label $top.title -text [mc "Create new branch"]
8164 grid $top.title - -pady 10
8165 label $top.id -text [mc "ID:"]
8166 entry $top.sha1 -width 40 -relief flat
8167 $top.sha1 insert 0 $rowmenuid
8168 $top.sha1 conf -state readonly
8169 grid $top.id $top.sha1 -sticky w
8170 label $top.nlab -text [mc "Name:"]
8171 entry $top.name -width 40
8172 bind $top.name <Key-Return> "[list mkbrgo $top]"
8173 grid $top.nlab $top.name -sticky w
8175 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
8176 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
8177 bind $top <Key-Return> [list mkbrgo $top]
8178 bind $top <Key-Escape> "catch {destroy $top}"
8179 grid $top.buts.go $top.buts.can
8180 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8181 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8182 grid $top.buts - -pady 10 -sticky ew
8187 global headids idheads
8189 set name [$top.name get]
8190 set id [$top.sha1 get]
8194 error_popup [mc "Please specify a name for the new branch"] $top
8197 if {[info exists headids($name)]} {
8198 if {![confirm_popup [mc \
8199 "Branch '%s' already exists. Overwrite?" $name] $top]} {
8202 set old_id $headids($name)
8205 catch {destroy $top}
8206 lappend cmdargs $name $id
8210 eval exec git branch $cmdargs
8216 if {$old_id ne {}} {
8222 set headids($name) $id
8223 lappend idheads($id) $name
8232 proc exec_citool {tool_args {baseid {}}} {
8233 global commitinfo env
8235 set save_env [array get env GIT_AUTHOR_*]
8237 if {$baseid ne {}} {
8238 if {![info exists commitinfo($baseid)]} {
8241 set author [lindex $commitinfo($baseid) 1]
8242 set date [lindex $commitinfo($baseid) 2]
8243 if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
8244 $author author name email]
8246 set env(GIT_AUTHOR_NAME) $name
8247 set env(GIT_AUTHOR_EMAIL) $email
8248 set env(GIT_AUTHOR_DATE) $date
8252 eval exec git citool $tool_args &
8254 array unset env GIT_AUTHOR_*
8255 array set env $save_env
8258 proc cherrypick {} {
8259 global rowmenuid curview
8260 global mainhead mainheadid
8262 set oldhead [exec git rev-parse HEAD]
8263 set dheads [descheads $rowmenuid]
8264 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
8265 set ok [confirm_popup [mc "Commit %s is already\
8266 included in branch %s -- really re-apply it?" \
8267 [string range $rowmenuid 0 7] $mainhead]]
8270 nowbusy cherrypick [mc "Cherry-picking"]
8272 # Unfortunately git-cherry-pick writes stuff to stderr even when
8273 # no error occurs, and exec takes that as an indication of error...
8274 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
8277 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
8279 error_popup [mc "Cherry-pick failed because of local changes\
8280 to file '%s'.\nPlease commit, reset or stash\
8281 your changes and try again." $fname]
8282 } elseif {[regexp -line \
8283 {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \
8285 if {[confirm_popup [mc "Cherry-pick failed because of merge\
8286 conflict.\nDo you wish to run git citool to\
8288 # Force citool to read MERGE_MSG
8289 file delete [file join [gitdir] "GITGUI_MSG"]
8290 exec_citool {} $rowmenuid
8298 set newhead [exec git rev-parse HEAD]
8299 if {$newhead eq $oldhead} {
8301 error_popup [mc "No changes committed"]
8304 addnewchild $newhead $oldhead
8305 if {[commitinview $oldhead $curview]} {
8306 insertrow $newhead $oldhead $curview
8307 if {$mainhead ne {}} {
8308 movehead $newhead $mainhead
8309 movedhead $newhead $mainhead
8311 set mainheadid $newhead
8320 global mainhead rowmenuid confirm_ok resettype
8323 set w ".confirmreset"
8326 wm title $w [mc "Confirm reset"]
8327 message $w.m -text \
8328 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
8329 -justify center -aspect 1000
8330 pack $w.m -side top -fill x -padx 20 -pady 20
8331 frame $w.f -relief sunken -border 2
8332 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
8333 grid $w.f.rt -sticky w
8335 radiobutton $w.f.soft -value soft -variable resettype -justify left \
8336 -text [mc "Soft: Leave working tree and index untouched"]
8337 grid $w.f.soft -sticky w
8338 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
8339 -text [mc "Mixed: Leave working tree untouched, reset index"]
8340 grid $w.f.mixed -sticky w
8341 radiobutton $w.f.hard -value hard -variable resettype -justify left \
8342 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
8343 grid $w.f.hard -sticky w
8344 pack $w.f -side top -fill x
8345 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
8346 pack $w.ok -side left -fill x -padx 20 -pady 20
8347 button $w.cancel -text [mc Cancel] -command "destroy $w"
8348 bind $w <Key-Escape> [list destroy $w]
8349 pack $w.cancel -side right -fill x -padx 20 -pady 20
8350 bind $w <Visibility> "grab $w; focus $w"
8352 if {!$confirm_ok} return
8353 if {[catch {set fd [open \
8354 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
8358 filerun $fd [list readresetstat $fd]
8359 nowbusy reset [mc "Resetting"]
8364 proc readresetstat {fd} {
8365 global mainhead mainheadid showlocalchanges rprogcoord
8367 if {[gets $fd line] >= 0} {
8368 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8369 set rprogcoord [expr {1.0 * $m / $n}]
8377 if {[catch {close $fd} err]} {
8380 set oldhead $mainheadid
8381 set newhead [exec git rev-parse HEAD]
8382 if {$newhead ne $oldhead} {
8383 movehead $newhead $mainhead
8384 movedhead $newhead $mainhead
8385 set mainheadid $newhead
8389 if {$showlocalchanges} {
8395 # context menu for a head
8396 proc headmenu {x y id head} {
8397 global headmenuid headmenuhead headctxmenu mainhead
8401 set headmenuhead $head
8403 if {$head eq $mainhead} {
8406 $headctxmenu entryconfigure 0 -state $state
8407 $headctxmenu entryconfigure 1 -state $state
8408 tk_popup $headctxmenu $x $y
8412 global headmenuid headmenuhead headids
8413 global showlocalchanges mainheadid
8415 # check the tree is clean first??
8416 nowbusy checkout [mc "Checking out"]
8420 set fd [open [list | git checkout $headmenuhead 2>@1] r]
8424 if {$showlocalchanges} {
8428 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
8432 proc readcheckoutstat {fd newhead newheadid} {
8433 global mainhead mainheadid headids showlocalchanges progresscoords
8435 if {[gets $fd line] >= 0} {
8436 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8437 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
8442 set progresscoords {0 0}
8445 if {[catch {close $fd} err]} {
8448 set oldmainid $mainheadid
8449 set mainhead $newhead
8450 set mainheadid $newheadid
8451 redrawtags $oldmainid
8452 redrawtags $newheadid
8454 if {$showlocalchanges} {
8460 global headmenuid headmenuhead mainhead
8463 set head $headmenuhead
8465 # this check shouldn't be needed any more...
8466 if {$head eq $mainhead} {
8467 error_popup [mc "Cannot delete the currently checked-out branch"]
8470 set dheads [descheads $id]
8471 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
8472 # the stuff on this branch isn't on any other branch
8473 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
8474 branch.\nReally delete branch %s?" $head $head]]} return
8478 if {[catch {exec git branch -D $head} err]} {
8483 removehead $id $head
8484 removedhead $id $head
8491 # Display a list of tags and heads
8493 global showrefstop bgcolor fgcolor selectbgcolor
8494 global bglist fglist reflistfilter reflist maincursor
8497 set showrefstop $top
8498 if {[winfo exists $top]} {
8504 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
8506 text $top.list -background $bgcolor -foreground $fgcolor \
8507 -selectbackground $selectbgcolor -font mainfont \
8508 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
8509 -width 30 -height 20 -cursor $maincursor \
8510 -spacing1 1 -spacing3 1 -state disabled
8511 $top.list tag configure highlight -background $selectbgcolor
8512 lappend bglist $top.list
8513 lappend fglist $top.list
8514 scrollbar $top.ysb -command "$top.list yview" -orient vertical
8515 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
8516 grid $top.list $top.ysb -sticky nsew
8517 grid $top.xsb x -sticky ew
8519 label $top.f.l -text "[mc "Filter"]: "
8520 entry $top.f.e -width 20 -textvariable reflistfilter
8521 set reflistfilter "*"
8522 trace add variable reflistfilter write reflistfilter_change
8523 pack $top.f.e -side right -fill x -expand 1
8524 pack $top.f.l -side left
8525 grid $top.f - -sticky ew -pady 2
8526 button $top.close -command [list destroy $top] -text [mc "Close"]
8527 bind $top <Key-Escape> [list destroy $top]
8529 grid columnconfigure $top 0 -weight 1
8530 grid rowconfigure $top 0 -weight 1
8531 bind $top.list <1> {break}
8532 bind $top.list <B1-Motion> {break}
8533 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
8538 proc sel_reflist {w x y} {
8539 global showrefstop reflist headids tagids otherrefids
8541 if {![winfo exists $showrefstop]} return
8542 set l [lindex [split [$w index "@$x,$y"] "."] 0]
8543 set ref [lindex $reflist [expr {$l-1}]]
8544 set n [lindex $ref 0]
8545 switch -- [lindex $ref 1] {
8546 "H" {selbyid $headids($n)}
8547 "T" {selbyid $tagids($n)}
8548 "o" {selbyid $otherrefids($n)}
8550 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
8553 proc unsel_reflist {} {
8556 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8557 $showrefstop.list tag remove highlight 0.0 end
8560 proc reflistfilter_change {n1 n2 op} {
8561 global reflistfilter
8563 after cancel refill_reflist
8564 after 200 refill_reflist
8567 proc refill_reflist {} {
8568 global reflist reflistfilter showrefstop headids tagids otherrefids
8571 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8573 foreach n [array names headids] {
8574 if {[string match $reflistfilter $n]} {
8575 if {[commitinview $headids($n) $curview]} {
8576 lappend refs [list $n H]
8578 interestedin $headids($n) {run refill_reflist}
8582 foreach n [array names tagids] {
8583 if {[string match $reflistfilter $n]} {
8584 if {[commitinview $tagids($n) $curview]} {
8585 lappend refs [list $n T]
8587 interestedin $tagids($n) {run refill_reflist}
8591 foreach n [array names otherrefids] {
8592 if {[string match $reflistfilter $n]} {
8593 if {[commitinview $otherrefids($n) $curview]} {
8594 lappend refs [list $n o]
8596 interestedin $otherrefids($n) {run refill_reflist}
8600 set refs [lsort -index 0 $refs]
8601 if {$refs eq $reflist} return
8603 # Update the contents of $showrefstop.list according to the
8604 # differences between $reflist (old) and $refs (new)
8605 $showrefstop.list conf -state normal
8606 $showrefstop.list insert end "\n"
8609 while {$i < [llength $reflist] || $j < [llength $refs]} {
8610 if {$i < [llength $reflist]} {
8611 if {$j < [llength $refs]} {
8612 set cmp [string compare [lindex $reflist $i 0] \
8613 [lindex $refs $j 0]]
8615 set cmp [string compare [lindex $reflist $i 1] \
8616 [lindex $refs $j 1]]
8626 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
8634 set l [expr {$j + 1}]
8635 $showrefstop.list image create $l.0 -align baseline \
8636 -image reficon-[lindex $refs $j 1] -padx 2
8637 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
8643 # delete last newline
8644 $showrefstop.list delete end-2c end-1c
8645 $showrefstop.list conf -state disabled
8648 # Stuff for finding nearby tags
8649 proc getallcommits {} {
8650 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
8651 global idheads idtags idotherrefs allparents tagobjid
8653 if {![info exists allcommits]} {
8659 set allccache [file join [gitdir] "gitk.cache"]
8661 set f [open $allccache r]
8670 set cmd [list | git rev-list --parents]
8671 set allcupdate [expr {$seeds ne {}}]
8675 set refs [concat [array names idheads] [array names idtags] \
8676 [array names idotherrefs]]
8679 foreach name [array names tagobjid] {
8680 lappend tagobjs $tagobjid($name)
8682 foreach id [lsort -unique $refs] {
8683 if {![info exists allparents($id)] &&
8684 [lsearch -exact $tagobjs $id] < 0} {
8695 set fd [open [concat $cmd $ids] r]
8696 fconfigure $fd -blocking 0
8699 filerun $fd [list getallclines $fd]
8705 # Since most commits have 1 parent and 1 child, we group strings of
8706 # such commits into "arcs" joining branch/merge points (BMPs), which
8707 # are commits that either don't have 1 parent or don't have 1 child.
8709 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8710 # arcout(id) - outgoing arcs for BMP
8711 # arcids(a) - list of IDs on arc including end but not start
8712 # arcstart(a) - BMP ID at start of arc
8713 # arcend(a) - BMP ID at end of arc
8714 # growing(a) - arc a is still growing
8715 # arctags(a) - IDs out of arcids (excluding end) that have tags
8716 # archeads(a) - IDs out of arcids (excluding end) that have heads
8717 # The start of an arc is at the descendent end, so "incoming" means
8718 # coming from descendents, and "outgoing" means going towards ancestors.
8720 proc getallclines {fd} {
8721 global allparents allchildren idtags idheads nextarc
8722 global arcnos arcids arctags arcout arcend arcstart archeads growing
8723 global seeds allcommits cachedarcs allcupdate
8726 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8727 set id [lindex $line 0]
8728 if {[info exists allparents($id)]} {
8733 set olds [lrange $line 1 end]
8734 set allparents($id) $olds
8735 if {![info exists allchildren($id)]} {
8736 set allchildren($id) {}
8741 if {[llength $olds] == 1 && [llength $a] == 1} {
8742 lappend arcids($a) $id
8743 if {[info exists idtags($id)]} {
8744 lappend arctags($a) $id
8746 if {[info exists idheads($id)]} {
8747 lappend archeads($a) $id
8749 if {[info exists allparents($olds)]} {
8750 # seen parent already
8751 if {![info exists arcout($olds)]} {
8754 lappend arcids($a) $olds
8755 set arcend($a) $olds
8758 lappend allchildren($olds) $id
8759 lappend arcnos($olds) $a
8763 foreach a $arcnos($id) {
8764 lappend arcids($a) $id
8771 lappend allchildren($p) $id
8772 set a [incr nextarc]
8773 set arcstart($a) $id
8780 if {[info exists allparents($p)]} {
8781 # seen it already, may need to make a new branch
8782 if {![info exists arcout($p)]} {
8785 lappend arcids($a) $p
8789 lappend arcnos($p) $a
8794 global cached_dheads cached_dtags cached_atags
8795 catch {unset cached_dheads}
8796 catch {unset cached_dtags}
8797 catch {unset cached_atags}
8800 return [expr {$nid >= 1000? 2: 1}]
8804 fconfigure $fd -blocking 1
8807 # got an error reading the list of commits
8808 # if we were updating, try rereading the whole thing again
8814 error_popup "[mc "Error reading commit topology information;\
8815 branch and preceding/following tag information\
8816 will be incomplete."]\n($err)"
8819 if {[incr allcommits -1] == 0} {
8829 proc recalcarc {a} {
8830 global arctags archeads arcids idtags idheads
8834 foreach id [lrange $arcids($a) 0 end-1] {
8835 if {[info exists idtags($id)]} {
8838 if {[info exists idheads($id)]} {
8843 set archeads($a) $ah
8847 global arcnos arcids nextarc arctags archeads idtags idheads
8848 global arcstart arcend arcout allparents growing
8851 if {[llength $a] != 1} {
8852 puts "oops splitarc called but [llength $a] arcs already"
8856 set i [lsearch -exact $arcids($a) $p]
8858 puts "oops splitarc $p not in arc $a"
8861 set na [incr nextarc]
8862 if {[info exists arcend($a)]} {
8863 set arcend($na) $arcend($a)
8865 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8866 set j [lsearch -exact $arcnos($l) $a]
8867 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8869 set tail [lrange $arcids($a) [expr {$i+1}] end]
8870 set arcids($a) [lrange $arcids($a) 0 $i]
8872 set arcstart($na) $p
8874 set arcids($na) $tail
8875 if {[info exists growing($a)]} {
8881 if {[llength $arcnos($id)] == 1} {
8884 set j [lsearch -exact $arcnos($id) $a]
8885 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8889 # reconstruct tags and heads lists
8890 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8895 set archeads($na) {}
8899 # Update things for a new commit added that is a child of one
8900 # existing commit. Used when cherry-picking.
8901 proc addnewchild {id p} {
8902 global allparents allchildren idtags nextarc
8903 global arcnos arcids arctags arcout arcend arcstart archeads growing
8904 global seeds allcommits
8906 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8907 set allparents($id) [list $p]
8908 set allchildren($id) {}
8911 lappend allchildren($p) $id
8912 set a [incr nextarc]
8913 set arcstart($a) $id
8916 set arcids($a) [list $p]
8918 if {![info exists arcout($p)]} {
8921 lappend arcnos($p) $a
8922 set arcout($id) [list $a]
8925 # This implements a cache for the topology information.
8926 # The cache saves, for each arc, the start and end of the arc,
8927 # the ids on the arc, and the outgoing arcs from the end.
8928 proc readcache {f} {
8929 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8930 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8935 if {$lim - $a > 500} {
8936 set lim [expr {$a + 500}]
8940 # finish reading the cache and setting up arctags, etc.
8942 if {$line ne "1"} {error "bad final version"}
8944 foreach id [array names idtags] {
8945 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8946 [llength $allparents($id)] == 1} {
8947 set a [lindex $arcnos($id) 0]
8948 if {$arctags($a) eq {}} {
8953 foreach id [array names idheads] {
8954 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8955 [llength $allparents($id)] == 1} {
8956 set a [lindex $arcnos($id) 0]
8957 if {$archeads($a) eq {}} {
8962 foreach id [lsort -unique $possible_seeds] {
8963 if {$arcnos($id) eq {}} {
8969 while {[incr a] <= $lim} {
8971 if {[llength $line] != 3} {error "bad line"}
8972 set s [lindex $line 0]
8974 lappend arcout($s) $a
8975 if {![info exists arcnos($s)]} {
8976 lappend possible_seeds $s
8979 set e [lindex $line 1]
8984 if {![info exists arcout($e)]} {
8988 set arcids($a) [lindex $line 2]
8989 foreach id $arcids($a) {
8990 lappend allparents($s) $id
8992 lappend arcnos($id) $a
8994 if {![info exists allparents($s)]} {
8995 set allparents($s) {}
9000 set nextarc [expr {$a - 1}]
9013 global nextarc cachedarcs possible_seeds
9017 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
9018 # make sure it's an integer
9019 set cachedarcs [expr {int([lindex $line 1])}]
9020 if {$cachedarcs < 0} {error "bad number of arcs"}
9022 set possible_seeds {}
9030 proc dropcache {err} {
9031 global allcwait nextarc cachedarcs seeds
9033 #puts "dropping cache ($err)"
9034 foreach v {arcnos arcout arcids arcstart arcend growing \
9035 arctags archeads allparents allchildren} {
9046 proc writecache {f} {
9047 global cachearc cachedarcs allccache
9048 global arcstart arcend arcnos arcids arcout
9052 if {$lim - $a > 1000} {
9053 set lim [expr {$a + 1000}]
9056 while {[incr a] <= $lim} {
9057 if {[info exists arcend($a)]} {
9058 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
9060 puts $f [list $arcstart($a) {} $arcids($a)]
9065 catch {file delete $allccache}
9066 #puts "writing cache failed ($err)"
9069 set cachearc [expr {$a - 1}]
9070 if {$a > $cachedarcs} {
9079 global nextarc cachedarcs cachearc allccache
9081 if {$nextarc == $cachedarcs} return
9083 set cachedarcs $nextarc
9085 set f [open $allccache w]
9086 puts $f [list 1 $cachedarcs]
9091 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
9092 # or 0 if neither is true.
9093 proc anc_or_desc {a b} {
9094 global arcout arcstart arcend arcnos cached_isanc
9096 if {$arcnos($a) eq $arcnos($b)} {
9097 # Both are on the same arc(s); either both are the same BMP,
9098 # or if one is not a BMP, the other is also not a BMP or is
9099 # the BMP at end of the arc (and it only has 1 incoming arc).
9100 # Or both can be BMPs with no incoming arcs.
9101 if {$a eq $b || $arcnos($a) eq {}} {
9104 # assert {[llength $arcnos($a)] == 1}
9105 set arc [lindex $arcnos($a) 0]
9106 set i [lsearch -exact $arcids($arc) $a]
9107 set j [lsearch -exact $arcids($arc) $b]
9108 if {$i < 0 || $i > $j} {
9115 if {![info exists arcout($a)]} {
9116 set arc [lindex $arcnos($a) 0]
9117 if {[info exists arcend($arc)]} {
9118 set aend $arcend($arc)
9122 set a $arcstart($arc)
9126 if {![info exists arcout($b)]} {
9127 set arc [lindex $arcnos($b) 0]
9128 if {[info exists arcend($arc)]} {
9129 set bend $arcend($arc)
9133 set b $arcstart($arc)
9143 if {[info exists cached_isanc($a,$bend)]} {
9144 if {$cached_isanc($a,$bend)} {
9148 if {[info exists cached_isanc($b,$aend)]} {
9149 if {$cached_isanc($b,$aend)} {
9152 if {[info exists cached_isanc($a,$bend)]} {
9157 set todo [list $a $b]
9160 for {set i 0} {$i < [llength $todo]} {incr i} {
9161 set x [lindex $todo $i]
9162 if {$anc($x) eq {}} {
9165 foreach arc $arcnos($x) {
9166 set xd $arcstart($arc)
9168 set cached_isanc($a,$bend) 1
9169 set cached_isanc($b,$aend) 0
9171 } elseif {$xd eq $aend} {
9172 set cached_isanc($b,$aend) 1
9173 set cached_isanc($a,$bend) 0
9176 if {![info exists anc($xd)]} {
9177 set anc($xd) $anc($x)
9179 } elseif {$anc($xd) ne $anc($x)} {
9184 set cached_isanc($a,$bend) 0
9185 set cached_isanc($b,$aend) 0
9189 # This identifies whether $desc has an ancestor that is
9190 # a growing tip of the graph and which is not an ancestor of $anc
9191 # and returns 0 if so and 1 if not.
9192 # If we subsequently discover a tag on such a growing tip, and that
9193 # turns out to be a descendent of $anc (which it could, since we
9194 # don't necessarily see children before parents), then $desc
9195 # isn't a good choice to display as a descendent tag of
9196 # $anc (since it is the descendent of another tag which is
9197 # a descendent of $anc). Similarly, $anc isn't a good choice to
9198 # display as a ancestor tag of $desc.
9200 proc is_certain {desc anc} {
9201 global arcnos arcout arcstart arcend growing problems
9204 if {[llength $arcnos($anc)] == 1} {
9205 # tags on the same arc are certain
9206 if {$arcnos($desc) eq $arcnos($anc)} {
9209 if {![info exists arcout($anc)]} {
9210 # if $anc is partway along an arc, use the start of the arc instead
9211 set a [lindex $arcnos($anc) 0]
9212 set anc $arcstart($a)
9215 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
9218 set a [lindex $arcnos($desc) 0]
9224 set anclist [list $x]
9228 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
9229 set x [lindex $anclist $i]
9234 foreach a $arcout($x) {
9235 if {[info exists growing($a)]} {
9236 if {![info exists growanc($x)] && $dl($x)} {
9242 if {[info exists dl($y)]} {
9246 if {![info exists done($y)]} {
9249 if {[info exists growanc($x)]} {
9253 for {set k 0} {$k < [llength $xl]} {incr k} {
9254 set z [lindex $xl $k]
9255 foreach c $arcout($z) {
9256 if {[info exists arcend($c)]} {
9258 if {[info exists dl($v)] && $dl($v)} {
9260 if {![info exists done($v)]} {
9263 if {[info exists growanc($v)]} {
9273 } elseif {$y eq $anc || !$dl($x)} {
9284 foreach x [array names growanc] {
9293 proc validate_arctags {a} {
9294 global arctags idtags
9298 foreach id $arctags($a) {
9300 if {![info exists idtags($id)]} {
9301 set na [lreplace $na $i $i]
9308 proc validate_archeads {a} {
9309 global archeads idheads
9312 set na $archeads($a)
9313 foreach id $archeads($a) {
9315 if {![info exists idheads($id)]} {
9316 set na [lreplace $na $i $i]
9320 set archeads($a) $na
9323 # Return the list of IDs that have tags that are descendents of id,
9324 # ignoring IDs that are descendents of IDs already reported.
9325 proc desctags {id} {
9326 global arcnos arcstart arcids arctags idtags allparents
9327 global growing cached_dtags
9329 if {![info exists allparents($id)]} {
9332 set t1 [clock clicks -milliseconds]
9334 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9335 # part-way along an arc; check that arc first
9336 set a [lindex $arcnos($id) 0]
9337 if {$arctags($a) ne {}} {
9339 set i [lsearch -exact $arcids($a) $id]
9341 foreach t $arctags($a) {
9342 set j [lsearch -exact $arcids($a) $t]
9350 set id $arcstart($a)
9351 if {[info exists idtags($id)]} {
9355 if {[info exists cached_dtags($id)]} {
9356 return $cached_dtags($id)
9363 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9364 set id [lindex $todo $i]
9366 set ta [info exists hastaggedancestor($id)]
9370 # ignore tags on starting node
9371 if {!$ta && $i > 0} {
9372 if {[info exists idtags($id)]} {
9375 } elseif {[info exists cached_dtags($id)]} {
9376 set tagloc($id) $cached_dtags($id)
9380 foreach a $arcnos($id) {
9382 if {!$ta && $arctags($a) ne {}} {
9384 if {$arctags($a) ne {}} {
9385 lappend tagloc($id) [lindex $arctags($a) end]
9388 if {$ta || $arctags($a) ne {}} {
9389 set tomark [list $d]
9390 for {set j 0} {$j < [llength $tomark]} {incr j} {
9391 set dd [lindex $tomark $j]
9392 if {![info exists hastaggedancestor($dd)]} {
9393 if {[info exists done($dd)]} {
9394 foreach b $arcnos($dd) {
9395 lappend tomark $arcstart($b)
9397 if {[info exists tagloc($dd)]} {
9400 } elseif {[info exists queued($dd)]} {
9403 set hastaggedancestor($dd) 1
9407 if {![info exists queued($d)]} {
9410 if {![info exists hastaggedancestor($d)]} {
9417 foreach id [array names tagloc] {
9418 if {![info exists hastaggedancestor($id)]} {
9419 foreach t $tagloc($id) {
9420 if {[lsearch -exact $tags $t] < 0} {
9426 set t2 [clock clicks -milliseconds]
9429 # remove tags that are descendents of other tags
9430 for {set i 0} {$i < [llength $tags]} {incr i} {
9431 set a [lindex $tags $i]
9432 for {set j 0} {$j < $i} {incr j} {
9433 set b [lindex $tags $j]
9434 set r [anc_or_desc $a $b]
9436 set tags [lreplace $tags $j $j]
9439 } elseif {$r == -1} {
9440 set tags [lreplace $tags $i $i]
9447 if {[array names growing] ne {}} {
9448 # graph isn't finished, need to check if any tag could get
9449 # eclipsed by another tag coming later. Simply ignore any
9450 # tags that could later get eclipsed.
9453 if {[is_certain $t $origid]} {
9457 if {$tags eq $ctags} {
9458 set cached_dtags($origid) $tags
9463 set cached_dtags($origid) $tags
9465 set t3 [clock clicks -milliseconds]
9466 if {0 && $t3 - $t1 >= 100} {
9467 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
9468 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9474 global arcnos arcids arcout arcend arctags idtags allparents
9475 global growing cached_atags
9477 if {![info exists allparents($id)]} {
9480 set t1 [clock clicks -milliseconds]
9482 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9483 # part-way along an arc; check that arc first
9484 set a [lindex $arcnos($id) 0]
9485 if {$arctags($a) ne {}} {
9487 set i [lsearch -exact $arcids($a) $id]
9488 foreach t $arctags($a) {
9489 set j [lsearch -exact $arcids($a) $t]
9495 if {![info exists arcend($a)]} {
9499 if {[info exists idtags($id)]} {
9503 if {[info exists cached_atags($id)]} {
9504 return $cached_atags($id)
9512 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9513 set id [lindex $todo $i]
9515 set td [info exists hastaggeddescendent($id)]
9519 # ignore tags on starting node
9520 if {!$td && $i > 0} {
9521 if {[info exists idtags($id)]} {
9524 } elseif {[info exists cached_atags($id)]} {
9525 set tagloc($id) $cached_atags($id)
9529 foreach a $arcout($id) {
9530 if {!$td && $arctags($a) ne {}} {
9532 if {$arctags($a) ne {}} {
9533 lappend tagloc($id) [lindex $arctags($a) 0]
9536 if {![info exists arcend($a)]} continue
9538 if {$td || $arctags($a) ne {}} {
9539 set tomark [list $d]
9540 for {set j 0} {$j < [llength $tomark]} {incr j} {
9541 set dd [lindex $tomark $j]
9542 if {![info exists hastaggeddescendent($dd)]} {
9543 if {[info exists done($dd)]} {
9544 foreach b $arcout($dd) {
9545 if {[info exists arcend($b)]} {
9546 lappend tomark $arcend($b)
9549 if {[info exists tagloc($dd)]} {
9552 } elseif {[info exists queued($dd)]} {
9555 set hastaggeddescendent($dd) 1
9559 if {![info exists queued($d)]} {
9562 if {![info exists hastaggeddescendent($d)]} {
9568 set t2 [clock clicks -milliseconds]
9571 foreach id [array names tagloc] {
9572 if {![info exists hastaggeddescendent($id)]} {
9573 foreach t $tagloc($id) {
9574 if {[lsearch -exact $tags $t] < 0} {
9581 # remove tags that are ancestors of other tags
9582 for {set i 0} {$i < [llength $tags]} {incr i} {
9583 set a [lindex $tags $i]
9584 for {set j 0} {$j < $i} {incr j} {
9585 set b [lindex $tags $j]
9586 set r [anc_or_desc $a $b]
9588 set tags [lreplace $tags $j $j]
9591 } elseif {$r == 1} {
9592 set tags [lreplace $tags $i $i]
9599 if {[array names growing] ne {}} {
9600 # graph isn't finished, need to check if any tag could get
9601 # eclipsed by another tag coming later. Simply ignore any
9602 # tags that could later get eclipsed.
9605 if {[is_certain $origid $t]} {
9609 if {$tags eq $ctags} {
9610 set cached_atags($origid) $tags
9615 set cached_atags($origid) $tags
9617 set t3 [clock clicks -milliseconds]
9618 if {0 && $t3 - $t1 >= 100} {
9619 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
9620 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9625 # Return the list of IDs that have heads that are descendents of id,
9626 # including id itself if it has a head.
9627 proc descheads {id} {
9628 global arcnos arcstart arcids archeads idheads cached_dheads
9631 if {![info exists allparents($id)]} {
9635 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9636 # part-way along an arc; check it first
9637 set a [lindex $arcnos($id) 0]
9638 if {$archeads($a) ne {}} {
9639 validate_archeads $a
9640 set i [lsearch -exact $arcids($a) $id]
9641 foreach t $archeads($a) {
9642 set j [lsearch -exact $arcids($a) $t]
9647 set id $arcstart($a)
9653 for {set i 0} {$i < [llength $todo]} {incr i} {
9654 set id [lindex $todo $i]
9655 if {[info exists cached_dheads($id)]} {
9656 set ret [concat $ret $cached_dheads($id)]
9658 if {[info exists idheads($id)]} {
9661 foreach a $arcnos($id) {
9662 if {$archeads($a) ne {}} {
9663 validate_archeads $a
9664 if {$archeads($a) ne {}} {
9665 set ret [concat $ret $archeads($a)]
9669 if {![info exists seen($d)]} {
9676 set ret [lsort -unique $ret]
9677 set cached_dheads($origid) $ret
9678 return [concat $ret $aret]
9681 proc addedtag {id} {
9682 global arcnos arcout cached_dtags cached_atags
9684 if {![info exists arcnos($id)]} return
9685 if {![info exists arcout($id)]} {
9686 recalcarc [lindex $arcnos($id) 0]
9688 catch {unset cached_dtags}
9689 catch {unset cached_atags}
9692 proc addedhead {hid head} {
9693 global arcnos arcout cached_dheads
9695 if {![info exists arcnos($hid)]} return
9696 if {![info exists arcout($hid)]} {
9697 recalcarc [lindex $arcnos($hid) 0]
9699 catch {unset cached_dheads}
9702 proc removedhead {hid head} {
9703 global cached_dheads
9705 catch {unset cached_dheads}
9708 proc movedhead {hid head} {
9709 global arcnos arcout cached_dheads
9711 if {![info exists arcnos($hid)]} return
9712 if {![info exists arcout($hid)]} {
9713 recalcarc [lindex $arcnos($hid) 0]
9715 catch {unset cached_dheads}
9718 proc changedrefs {} {
9719 global cached_dheads cached_dtags cached_atags
9720 global arctags archeads arcnos arcout idheads idtags
9722 foreach id [concat [array names idheads] [array names idtags]] {
9723 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9724 set a [lindex $arcnos($id) 0]
9725 if {![info exists donearc($a)]} {
9731 catch {unset cached_dtags}
9732 catch {unset cached_atags}
9733 catch {unset cached_dheads}
9736 proc rereadrefs {} {
9737 global idtags idheads idotherrefs mainheadid
9739 set refids [concat [array names idtags] \
9740 [array names idheads] [array names idotherrefs]]
9741 foreach id $refids {
9742 if {![info exists ref($id)]} {
9743 set ref($id) [listrefs $id]
9746 set oldmainhead $mainheadid
9749 set refids [lsort -unique [concat $refids [array names idtags] \
9750 [array names idheads] [array names idotherrefs]]]
9751 foreach id $refids {
9752 set v [listrefs $id]
9753 if {![info exists ref($id)] || $ref($id) != $v} {
9757 if {$oldmainhead ne $mainheadid} {
9758 redrawtags $oldmainhead
9759 redrawtags $mainheadid
9764 proc listrefs {id} {
9765 global idtags idheads idotherrefs
9768 if {[info exists idtags($id)]} {
9772 if {[info exists idheads($id)]} {
9776 if {[info exists idotherrefs($id)]} {
9777 set z $idotherrefs($id)
9779 return [list $x $y $z]
9782 proc showtag {tag isnew} {
9783 global ctext tagcontents tagids linknum tagobjid
9786 addtohistory [list showtag $tag 0]
9788 $ctext conf -state normal
9792 if {![info exists tagcontents($tag)]} {
9794 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9797 if {[info exists tagcontents($tag)]} {
9798 set text $tagcontents($tag)
9800 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9802 appendwithlinks $text {}
9803 $ctext conf -state disabled
9815 if {[info exists gitktmpdir]} {
9816 catch {file delete -force $gitktmpdir}
9820 proc mkfontdisp {font top which} {
9821 global fontattr fontpref $font
9823 set fontpref($font) [set $font]
9824 button $top.${font}but -text $which -font optionfont \
9825 -command [list choosefont $font $which]
9826 label $top.$font -relief flat -font $font \
9827 -text $fontattr($font,family) -justify left
9828 grid x $top.${font}but $top.$font -sticky w
9831 proc choosefont {font which} {
9832 global fontparam fontlist fonttop fontattr
9835 set fontparam(which) $which
9836 set fontparam(font) $font
9837 set fontparam(family) [font actual $font -family]
9838 set fontparam(size) $fontattr($font,size)
9839 set fontparam(weight) $fontattr($font,weight)
9840 set fontparam(slant) $fontattr($font,slant)
9843 if {![winfo exists $top]} {
9845 eval font config sample [font actual $font]
9847 wm transient $top $prefstop
9848 wm title $top [mc "Gitk font chooser"]
9849 label $top.l -textvariable fontparam(which)
9850 pack $top.l -side top
9851 set fontlist [lsort [font families]]
9853 listbox $top.f.fam -listvariable fontlist \
9854 -yscrollcommand [list $top.f.sb set]
9855 bind $top.f.fam <<ListboxSelect>> selfontfam
9856 scrollbar $top.f.sb -command [list $top.f.fam yview]
9857 pack $top.f.sb -side right -fill y
9858 pack $top.f.fam -side left -fill both -expand 1
9859 pack $top.f -side top -fill both -expand 1
9861 spinbox $top.g.size -from 4 -to 40 -width 4 \
9862 -textvariable fontparam(size) \
9863 -validatecommand {string is integer -strict %s}
9864 checkbutton $top.g.bold -padx 5 \
9865 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9866 -variable fontparam(weight) -onvalue bold -offvalue normal
9867 checkbutton $top.g.ital -padx 5 \
9868 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9869 -variable fontparam(slant) -onvalue italic -offvalue roman
9870 pack $top.g.size $top.g.bold $top.g.ital -side left
9871 pack $top.g -side top
9872 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9874 $top.c create text 100 25 -anchor center -text $which -font sample \
9875 -fill black -tags text
9876 bind $top.c <Configure> [list centertext $top.c]
9877 pack $top.c -side top -fill x
9879 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9880 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9881 bind $top <Key-Return> fontok
9882 bind $top <Key-Escape> fontcan
9883 grid $top.buts.ok $top.buts.can
9884 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9885 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9886 pack $top.buts -side bottom -fill x
9887 trace add variable fontparam write chg_fontparam
9890 $top.c itemconf text -text $which
9892 set i [lsearch -exact $fontlist $fontparam(family)]
9894 $top.f.fam selection set $i
9899 proc centertext {w} {
9900 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9904 global fontparam fontpref prefstop
9906 set f $fontparam(font)
9907 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9908 if {$fontparam(weight) eq "bold"} {
9909 lappend fontpref($f) "bold"
9911 if {$fontparam(slant) eq "italic"} {
9912 lappend fontpref($f) "italic"
9915 $w conf -text $fontparam(family) -font $fontpref($f)
9921 global fonttop fontparam
9923 if {[info exists fonttop]} {
9924 catch {destroy $fonttop}
9925 catch {font delete sample}
9931 proc selfontfam {} {
9932 global fonttop fontparam
9934 set i [$fonttop.f.fam curselection]
9936 set fontparam(family) [$fonttop.f.fam get $i]
9940 proc chg_fontparam {v sub op} {
9943 font config sample -$sub $fontparam($sub)
9947 global maxwidth maxgraphpct
9948 global oldprefs prefstop showneartags showlocalchanges
9949 global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
9950 global tabstop limitdiffs autoselect extdifftool perfile_attrs
9954 if {[winfo exists $top]} {
9958 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9959 limitdiffs tabstop perfile_attrs} {
9960 set oldprefs($v) [set $v]
9963 wm title $top [mc "Gitk preferences"]
9965 label $top.ldisp -text [mc "Commit list display options"]
9966 grid $top.ldisp - -sticky w -pady 10
9967 label $top.spacer -text " "
9968 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9970 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9971 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9972 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9974 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9975 grid x $top.maxpctl $top.maxpct -sticky w
9976 frame $top.showlocal
9977 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9978 checkbutton $top.showlocal.b -variable showlocalchanges
9979 pack $top.showlocal.b $top.showlocal.l -side left
9980 grid x $top.showlocal -sticky w
9981 frame $top.autoselect
9982 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9983 checkbutton $top.autoselect.b -variable autoselect
9984 pack $top.autoselect.b $top.autoselect.l -side left
9985 grid x $top.autoselect -sticky w
9987 label $top.ddisp -text [mc "Diff display options"]
9988 grid $top.ddisp - -sticky w -pady 10
9989 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9990 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9991 grid x $top.tabstopl $top.tabstop -sticky w
9993 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9994 checkbutton $top.ntag.b -variable showneartags
9995 pack $top.ntag.b $top.ntag.l -side left
9996 grid x $top.ntag -sticky w
9998 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9999 checkbutton $top.ldiff.b -variable limitdiffs
10000 pack $top.ldiff.b $top.ldiff.l -side left
10001 grid x $top.ldiff -sticky w
10003 label $top.lattr.l -text [mc "Support per-file encodings"] -font optionfont
10004 checkbutton $top.lattr.b -variable perfile_attrs
10005 pack $top.lattr.b $top.lattr.l -side left
10006 grid x $top.lattr -sticky w
10008 entry $top.extdifft -textvariable extdifftool
10009 frame $top.extdifff
10010 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
10012 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
10013 -command choose_extdiff
10014 pack $top.extdifff.l $top.extdifff.b -side left
10015 grid x $top.extdifff $top.extdifft -sticky w
10017 label $top.cdisp -text [mc "Colors: press to choose"]
10018 grid $top.cdisp - -sticky w -pady 10
10019 label $top.bg -padx 40 -relief sunk -background $bgcolor
10020 button $top.bgbut -text [mc "Background"] -font optionfont \
10021 -command [list choosecolor bgcolor {} $top.bg background setbg]
10022 grid x $top.bgbut $top.bg -sticky w
10023 label $top.fg -padx 40 -relief sunk -background $fgcolor
10024 button $top.fgbut -text [mc "Foreground"] -font optionfont \
10025 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
10026 grid x $top.fgbut $top.fg -sticky w
10027 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
10028 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
10029 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
10030 [list $ctext tag conf d0 -foreground]]
10031 grid x $top.diffoldbut $top.diffold -sticky w
10032 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
10033 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
10034 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
10035 [list $ctext tag conf dresult -foreground]]
10036 grid x $top.diffnewbut $top.diffnew -sticky w
10037 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
10038 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
10039 -command [list choosecolor diffcolors 2 $top.hunksep \
10040 "diff hunk header" \
10041 [list $ctext tag conf hunksep -foreground]]
10042 grid x $top.hunksepbut $top.hunksep -sticky w
10043 label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
10044 button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \
10045 -command [list choosecolor markbgcolor {} $top.markbgsep \
10046 [mc "marked line background"] \
10047 [list $ctext tag conf omark -background]]
10048 grid x $top.markbgbut $top.markbgsep -sticky w
10049 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
10050 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
10051 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
10052 grid x $top.selbgbut $top.selbgsep -sticky w
10054 label $top.cfont -text [mc "Fonts: press to choose"]
10055 grid $top.cfont - -sticky w -pady 10
10056 mkfontdisp mainfont $top [mc "Main font"]
10057 mkfontdisp textfont $top [mc "Diff display font"]
10058 mkfontdisp uifont $top [mc "User interface font"]
10061 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
10062 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
10063 bind $top <Key-Return> prefsok
10064 bind $top <Key-Escape> prefscan
10065 grid $top.buts.ok $top.buts.can
10066 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10067 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10068 grid $top.buts - - -pady 10 -sticky ew
10069 bind $top <Visibility> "focus $top.buts.ok"
10072 proc choose_extdiff {} {
10075 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
10077 set extdifftool $prog
10081 proc choosecolor {v vi w x cmd} {
10084 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
10085 -title [mc "Gitk: choose color for %s" $x]]
10086 if {$c eq {}} return
10087 $w conf -background $c
10092 proc setselbg {c} {
10093 global bglist cflist
10094 foreach w $bglist {
10095 $w configure -selectbackground $c
10097 $cflist tag configure highlight \
10098 -background [$cflist cget -selectbackground]
10099 allcanvs itemconf secsel -fill $c
10105 foreach w $bglist {
10106 $w conf -background $c
10113 foreach w $fglist {
10114 $w conf -foreground $c
10116 allcanvs itemconf text -fill $c
10117 $canv itemconf circle -outline $c
10121 global oldprefs prefstop
10123 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10124 limitdiffs tabstop perfile_attrs} {
10126 set $v $oldprefs($v)
10128 catch {destroy $prefstop}
10134 global maxwidth maxgraphpct
10135 global oldprefs prefstop showneartags showlocalchanges
10136 global fontpref mainfont textfont uifont
10137 global limitdiffs treediffs perfile_attrs
10139 catch {destroy $prefstop}
10143 if {$mainfont ne $fontpref(mainfont)} {
10144 set mainfont $fontpref(mainfont)
10145 parsefont mainfont $mainfont
10146 eval font configure mainfont [fontflags mainfont]
10147 eval font configure mainfontbold [fontflags mainfont 1]
10151 if {$textfont ne $fontpref(textfont)} {
10152 set textfont $fontpref(textfont)
10153 parsefont textfont $textfont
10154 eval font configure textfont [fontflags textfont]
10155 eval font configure textfontbold [fontflags textfont 1]
10157 if {$uifont ne $fontpref(uifont)} {
10158 set uifont $fontpref(uifont)
10159 parsefont uifont $uifont
10160 eval font configure uifont [fontflags uifont]
10163 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
10164 if {$showlocalchanges} {
10170 if {$limitdiffs != $oldprefs(limitdiffs) ||
10171 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
10172 # treediffs elements are limited by path;
10173 # won't have encodings cached if perfile_attrs was just turned on
10174 catch {unset treediffs}
10176 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
10177 || $maxgraphpct != $oldprefs(maxgraphpct)} {
10179 } elseif {$showneartags != $oldprefs(showneartags) ||
10180 $limitdiffs != $oldprefs(limitdiffs)} {
10185 proc formatdate {d} {
10186 global datetimeformat
10188 set d [clock format $d -format $datetimeformat]
10193 # This list of encoding names and aliases is distilled from
10194 # http://www.iana.org/assignments/character-sets.
10195 # Not all of them are supported by Tcl.
10196 set encoding_aliases {
10197 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
10198 ISO646-US US-ASCII us IBM367 cp367 csASCII }
10199 { ISO-10646-UTF-1 csISO10646UTF1 }
10200 { ISO_646.basic:1983 ref csISO646basic1983 }
10201 { INVARIANT csINVARIANT }
10202 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
10203 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
10204 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
10205 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
10206 { NATS-DANO iso-ir-9-1 csNATSDANO }
10207 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
10208 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
10209 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
10210 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
10211 { ISO-2022-KR csISO2022KR }
10213 { ISO-2022-JP csISO2022JP }
10214 { ISO-2022-JP-2 csISO2022JP2 }
10215 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
10216 csISO13JISC6220jp }
10217 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
10218 { IT iso-ir-15 ISO646-IT csISO15Italian }
10219 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
10220 { ES iso-ir-17 ISO646-ES csISO17Spanish }
10221 { greek7-old iso-ir-18 csISO18Greek7Old }
10222 { latin-greek iso-ir-19 csISO19LatinGreek }
10223 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
10224 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
10225 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
10226 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
10227 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
10228 { BS_viewdata iso-ir-47 csISO47BSViewdata }
10229 { INIS iso-ir-49 csISO49INIS }
10230 { INIS-8 iso-ir-50 csISO50INIS8 }
10231 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
10232 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
10233 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
10234 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
10235 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
10236 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
10237 csISO60Norwegian1 }
10238 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
10239 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
10240 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
10241 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
10242 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
10243 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
10244 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
10245 { greek7 iso-ir-88 csISO88Greek7 }
10246 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
10247 { iso-ir-90 csISO90 }
10248 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
10249 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
10250 csISO92JISC62991984b }
10251 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
10252 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
10253 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
10254 csISO95JIS62291984handadd }
10255 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
10256 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
10257 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
10258 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
10259 CP819 csISOLatin1 }
10260 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
10261 { T.61-7bit iso-ir-102 csISO102T617bit }
10262 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
10263 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
10264 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
10265 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
10266 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
10267 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
10268 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
10269 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
10270 arabic csISOLatinArabic }
10271 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
10272 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
10273 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
10274 greek greek8 csISOLatinGreek }
10275 { T.101-G2 iso-ir-128 csISO128T101G2 }
10276 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
10278 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
10279 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
10280 { CSN_369103 iso-ir-139 csISO139CSN369103 }
10281 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
10282 { ISO_6937-2-add iso-ir-142 csISOTextComm }
10283 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
10284 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
10285 csISOLatinCyrillic }
10286 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
10287 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
10288 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
10289 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
10290 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
10291 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
10292 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
10293 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
10294 { ISO_10367-box iso-ir-155 csISO10367Box }
10295 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
10296 { latin-lap lap iso-ir-158 csISO158Lap }
10297 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
10298 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
10301 { JIS_X0201 X0201 csHalfWidthKatakana }
10302 { KSC5636 ISO646-KR csKSC5636 }
10303 { ISO-10646-UCS-2 csUnicode }
10304 { ISO-10646-UCS-4 csUCS4 }
10305 { DEC-MCS dec csDECMCS }
10306 { hp-roman8 roman8 r8 csHPRoman8 }
10307 { macintosh mac csMacintosh }
10308 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
10310 { IBM038 EBCDIC-INT cp038 csIBM038 }
10311 { IBM273 CP273 csIBM273 }
10312 { IBM274 EBCDIC-BE CP274 csIBM274 }
10313 { IBM275 EBCDIC-BR cp275 csIBM275 }
10314 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
10315 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
10316 { IBM280 CP280 ebcdic-cp-it csIBM280 }
10317 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
10318 { IBM284 CP284 ebcdic-cp-es csIBM284 }
10319 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
10320 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
10321 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
10322 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
10323 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
10324 { IBM424 cp424 ebcdic-cp-he csIBM424 }
10325 { IBM437 cp437 437 csPC8CodePage437 }
10326 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
10327 { IBM775 cp775 csPC775Baltic }
10328 { IBM850 cp850 850 csPC850Multilingual }
10329 { IBM851 cp851 851 csIBM851 }
10330 { IBM852 cp852 852 csPCp852 }
10331 { IBM855 cp855 855 csIBM855 }
10332 { IBM857 cp857 857 csIBM857 }
10333 { IBM860 cp860 860 csIBM860 }
10334 { IBM861 cp861 861 cp-is csIBM861 }
10335 { IBM862 cp862 862 csPC862LatinHebrew }
10336 { IBM863 cp863 863 csIBM863 }
10337 { IBM864 cp864 csIBM864 }
10338 { IBM865 cp865 865 csIBM865 }
10339 { IBM866 cp866 866 csIBM866 }
10340 { IBM868 CP868 cp-ar csIBM868 }
10341 { IBM869 cp869 869 cp-gr csIBM869 }
10342 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
10343 { IBM871 CP871 ebcdic-cp-is csIBM871 }
10344 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
10345 { IBM891 cp891 csIBM891 }
10346 { IBM903 cp903 csIBM903 }
10347 { IBM904 cp904 904 csIBBM904 }
10348 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
10349 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
10350 { IBM1026 CP1026 csIBM1026 }
10351 { EBCDIC-AT-DE csIBMEBCDICATDE }
10352 { EBCDIC-AT-DE-A csEBCDICATDEA }
10353 { EBCDIC-CA-FR csEBCDICCAFR }
10354 { EBCDIC-DK-NO csEBCDICDKNO }
10355 { EBCDIC-DK-NO-A csEBCDICDKNOA }
10356 { EBCDIC-FI-SE csEBCDICFISE }
10357 { EBCDIC-FI-SE-A csEBCDICFISEA }
10358 { EBCDIC-FR csEBCDICFR }
10359 { EBCDIC-IT csEBCDICIT }
10360 { EBCDIC-PT csEBCDICPT }
10361 { EBCDIC-ES csEBCDICES }
10362 { EBCDIC-ES-A csEBCDICESA }
10363 { EBCDIC-ES-S csEBCDICESS }
10364 { EBCDIC-UK csEBCDICUK }
10365 { EBCDIC-US csEBCDICUS }
10366 { UNKNOWN-8BIT csUnknown8BiT }
10367 { MNEMONIC csMnemonic }
10369 { VISCII csVISCII }
10372 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
10373 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
10374 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
10375 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
10376 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
10377 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
10378 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
10379 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
10380 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
10381 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
10382 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
10383 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
10384 { IBM1047 IBM-1047 }
10385 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
10386 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
10387 { UNICODE-1-1 csUnicode11 }
10388 { CESU-8 csCESU-8 }
10389 { BOCU-1 csBOCU-1 }
10390 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
10391 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
10393 { ISO-8859-15 ISO_8859-15 Latin-9 }
10394 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
10395 { GBK CP936 MS936 windows-936 }
10396 { JIS_Encoding csJISEncoding }
10397 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
10398 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
10400 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
10401 { ISO-10646-UCS-Basic csUnicodeASCII }
10402 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
10403 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
10404 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
10405 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
10406 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
10407 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
10408 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
10409 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
10410 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
10411 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
10412 { Adobe-Standard-Encoding csAdobeStandardEncoding }
10413 { Ventura-US csVenturaUS }
10414 { Ventura-International csVenturaInternational }
10415 { PC8-Danish-Norwegian csPC8DanishNorwegian }
10416 { PC8-Turkish csPC8Turkish }
10417 { IBM-Symbols csIBMSymbols }
10418 { IBM-Thai csIBMThai }
10419 { HP-Legal csHPLegal }
10420 { HP-Pi-font csHPPiFont }
10421 { HP-Math8 csHPMath8 }
10422 { Adobe-Symbol-Encoding csHPPSMath }
10423 { HP-DeskTop csHPDesktop }
10424 { Ventura-Math csVenturaMath }
10425 { Microsoft-Publishing csMicrosoftPublishing }
10426 { Windows-31J csWindows31J }
10427 { GB2312 csGB2312 }
10431 proc tcl_encoding {enc} {
10432 global encoding_aliases tcl_encoding_cache
10433 if {[info exists tcl_encoding_cache($enc)]} {
10434 return $tcl_encoding_cache($enc)
10436 set names [encoding names]
10437 set lcnames [string tolower $names]
10438 set enc [string tolower $enc]
10439 set i [lsearch -exact $lcnames $enc]
10441 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
10442 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
10443 set i [lsearch -exact $lcnames $encx]
10447 foreach l $encoding_aliases {
10448 set ll [string tolower $l]
10449 if {[lsearch -exact $ll $enc] < 0} continue
10450 # look through the aliases for one that tcl knows about
10452 set i [lsearch -exact $lcnames $e]
10454 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
10455 set i [lsearch -exact $lcnames $ex]
10465 set tclenc [lindex $names $i]
10467 set tcl_encoding_cache($enc) $tclenc
10471 proc gitattr {path attr default} {
10472 global path_attr_cache
10473 if {[info exists path_attr_cache($attr,$path)]} {
10474 set r $path_attr_cache($attr,$path)
10476 set r "unspecified"
10477 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
10478 regexp "(.*): encoding: (.*)" $line m f r
10480 set path_attr_cache($attr,$path) $r
10482 if {$r eq "unspecified"} {
10488 proc cache_gitattr {attr pathlist} {
10489 global path_attr_cache
10491 foreach path $pathlist {
10492 if {![info exists path_attr_cache($attr,$path)]} {
10493 lappend newlist $path
10497 if {[tk windowingsystem] == "win32"} {
10498 # windows has a 32k limit on the arguments to a command...
10501 while {$newlist ne {}} {
10502 set head [lrange $newlist 0 [expr {$lim - 1}]]
10503 set newlist [lrange $newlist $lim end]
10504 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
10505 foreach row [split $rlist "\n"] {
10506 if {[regexp "(.*): encoding: (.*)" $row m path value]} {
10507 if {[string index $path 0] eq "\""} {
10508 set path [encoding convertfrom [lindex $path 0]]
10510 set path_attr_cache($attr,$path) $value
10517 proc get_path_encoding {path} {
10518 global gui_encoding perfile_attrs
10519 set tcl_enc $gui_encoding
10520 if {$path ne {} && $perfile_attrs} {
10521 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
10529 # First check that Tcl/Tk is recent enough
10530 if {[catch {package require Tk 8.4} err]} {
10531 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
10532 Gitk requires at least Tcl/Tk 8.4."]
10537 set wrcomcmd "git diff-tree --stdin -p --pretty"
10541 set gitencoding [exec git config --get i18n.commitencoding]
10543 if {$gitencoding == ""} {
10544 set gitencoding "utf-8"
10546 set tclencoding [tcl_encoding $gitencoding]
10547 if {$tclencoding == {}} {
10548 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
10551 set gui_encoding [encoding system]
10553 set enc [exec git config --get gui.encoding]
10555 set tclenc [tcl_encoding $enc]
10556 if {$tclenc ne {}} {
10557 set gui_encoding $tclenc
10559 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
10564 set mainfont {Helvetica 9}
10565 set textfont {Courier 9}
10566 set uifont {Helvetica 9 bold}
10568 set findmergefiles 0
10576 set cmitmode "patch"
10577 set wrapcomment "none"
10581 set showlocalchanges 1
10583 set datetimeformat "%Y-%m-%d %H:%M:%S"
10585 set perfile_attrs 0
10587 set extdifftool "meld"
10589 set colors {green red blue magenta darkgrey brown orange}
10592 set diffcolors {red "#00a000" blue}
10595 set selectbgcolor gray85
10596 set markbgcolor "#e0e0ff"
10598 set circlecolors {white blue gray blue blue}
10600 # button for popping up context menus
10601 if {[tk windowingsystem] eq "aqua"} {
10602 set ctxbut <Button-2>
10604 set ctxbut <Button-3>
10607 ## For msgcat loading, first locate the installation location.
10608 if { [info exists ::env(GITK_MSGSDIR)] } {
10609 ## Msgsdir was manually set in the environment.
10610 set gitk_msgsdir $::env(GITK_MSGSDIR)
10612 ## Let's guess the prefix from argv0.
10613 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
10614 set gitk_libdir [file join $gitk_prefix share gitk lib]
10615 set gitk_msgsdir [file join $gitk_libdir msgs]
10619 ## Internationalization (i18n) through msgcat and gettext. See
10620 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
10621 package require msgcat
10622 namespace import ::msgcat::mc
10623 ## And eventually load the actual message catalog
10624 ::msgcat::mcload $gitk_msgsdir
10626 catch {source ~/.gitk}
10628 font create optionfont -family sans-serif -size -12
10630 parsefont mainfont $mainfont
10631 eval font create mainfont [fontflags mainfont]
10632 eval font create mainfontbold [fontflags mainfont 1]
10634 parsefont textfont $textfont
10635 eval font create textfont [fontflags textfont]
10636 eval font create textfontbold [fontflags textfont 1]
10638 parsefont uifont $uifont
10639 eval font create uifont [fontflags uifont]
10643 # check that we can find a .git directory somewhere...
10644 if {[catch {set gitdir [gitdir]}]} {
10645 show_error {} . [mc "Cannot find a git repository here."]
10648 if {![file isdirectory $gitdir]} {
10649 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
10654 set selectheadid {}
10657 set cmdline_files {}
10659 set revtreeargscmd {}
10660 foreach arg $argv {
10661 switch -glob -- $arg {
10664 set cmdline_files [lrange $argv [expr {$i + 1}] end]
10667 "--select-commit=*" {
10668 set selecthead [string range $arg 16 end]
10671 set revtreeargscmd [string range $arg 10 end]
10674 lappend revtreeargs $arg
10680 if {$selecthead eq "HEAD"} {
10684 if {$i >= [llength $argv] && $revtreeargs ne {}} {
10685 # no -- on command line, but some arguments (other than --argscmd)
10687 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
10688 set cmdline_files [split $f "\n"]
10689 set n [llength $cmdline_files]
10690 set revtreeargs [lrange $revtreeargs 0 end-$n]
10691 # Unfortunately git rev-parse doesn't produce an error when
10692 # something is both a revision and a filename. To be consistent
10693 # with git log and git rev-list, check revtreeargs for filenames.
10694 foreach arg $revtreeargs {
10695 if {[file exists $arg]} {
10696 show_error {} . [mc "Ambiguous argument '%s': both revision\
10697 and filename" $arg]
10702 # unfortunately we get both stdout and stderr in $err,
10703 # so look for "fatal:".
10704 set i [string first "fatal:" $err]
10706 set err [string range $err [expr {$i + 6}] end]
10708 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
10713 set nullid "0000000000000000000000000000000000000000"
10714 set nullid2 "0000000000000000000000000000000000000001"
10715 set nullfile "/dev/null"
10717 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
10724 set highlight_paths {}
10726 set searchdirn -forwards
10728 set boldnamerows {}
10729 set diffelide {0 0}
10730 set markingmatches 0
10731 set linkentercount 0
10732 set need_redisplay 0
10739 set selectedhlview [mc "None"]
10740 set highlight_related [mc "None"]
10741 set highlight_files {}
10742 set viewfiles(0) {}
10745 set viewargscmd(0) {}
10747 set selectedline {}
10755 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
10758 # wait for the window to become visible
10759 tkwait visibility .
10760 wm title . "[file tail $argv0]: [file tail [pwd]]"
10763 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
10764 # create a view for the files/dirs specified on the command line
10768 set viewname(1) [mc "Command line"]
10769 set viewfiles(1) $cmdline_files
10770 set viewargs(1) $revtreeargs
10771 set viewargscmd(1) $revtreeargscmd
10775 .bar.view entryconf [mca "Edit view..."] -state normal
10776 .bar.view entryconf [mca "Delete view"] -state normal
10779 if {[info exists permviews]} {
10780 foreach v $permviews {
10783 set viewname($n) [lindex $v 0]
10784 set viewfiles($n) [lindex $v 1]
10785 set viewargs($n) [lindex $v 2]
10786 set viewargscmd($n) [lindex $v 3]