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 do_readcommit {id} {
1561 # Invoke git-log to handle automatic encoding conversion
1562 set fd [open [concat | git log --no-color --pretty=raw -1 $id] r]
1563 # Read the results using i18n.logoutputencoding
1564 fconfigure $fd -translation lf -eofchar {}
1565 if {$tclencoding != {}} {
1566 fconfigure $fd -encoding $tclencoding
1568 set contents [read $fd]
1570 # Remove the heading line
1571 regsub {^commit [0-9a-f]+\n} $contents {} contents
1576 proc readcommit {id} {
1577 if {[catch {set contents [do_readcommit $id]}]} return
1578 parsecommit $id $contents 1
1581 proc parsecommit {id contents listed} {
1582 global commitinfo cdate
1591 set hdrend [string first "\n\n" $contents]
1593 # should never happen...
1594 set hdrend [string length $contents]
1596 set header [string range $contents 0 [expr {$hdrend - 1}]]
1597 set comment [string range $contents [expr {$hdrend + 2}] end]
1598 foreach line [split $header "\n"] {
1599 set tag [lindex $line 0]
1600 if {$tag == "author"} {
1601 set audate [lindex $line end-1]
1602 set auname [lrange $line 1 end-2]
1603 } elseif {$tag == "committer"} {
1604 set comdate [lindex $line end-1]
1605 set comname [lrange $line 1 end-2]
1609 # take the first non-blank line of the comment as the headline
1610 set headline [string trimleft $comment]
1611 set i [string first "\n" $headline]
1613 set headline [string range $headline 0 $i]
1615 set headline [string trimright $headline]
1616 set i [string first "\r" $headline]
1618 set headline [string trimright [string range $headline 0 $i]]
1621 # git log indents the comment by 4 spaces;
1622 # if we got this via git cat-file, add the indentation
1624 foreach line [split $comment "\n"] {
1625 append newcomment " "
1626 append newcomment $line
1627 append newcomment "\n"
1629 set comment $newcomment
1631 if {$comdate != {}} {
1632 set cdate($id) $comdate
1634 set commitinfo($id) [list $headline $auname $audate \
1635 $comname $comdate $comment]
1638 proc getcommit {id} {
1639 global commitdata commitinfo
1641 if {[info exists commitdata($id)]} {
1642 parsecommit $id $commitdata($id) 1
1645 if {![info exists commitinfo($id)]} {
1646 set commitinfo($id) [list [mc "No commit information available"]]
1652 # Expand an abbreviated commit ID to a list of full 40-char IDs that match
1653 # and are present in the current view.
1654 # This is fairly slow...
1655 proc longid {prefix} {
1656 global varcid curview
1659 foreach match [array names varcid "$curview,$prefix*"] {
1660 lappend ids [lindex [split $match ","] 1]
1666 global tagids idtags headids idheads tagobjid
1667 global otherrefids idotherrefs mainhead mainheadid
1668 global selecthead selectheadid
1670 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1673 set refd [open [list | git show-ref -d] r]
1674 while {[gets $refd line] >= 0} {
1675 if {[string index $line 40] ne " "} continue
1676 set id [string range $line 0 39]
1677 set ref [string range $line 41 end]
1678 if {![string match "refs/*" $ref]} continue
1679 set name [string range $ref 5 end]
1680 if {[string match "remotes/*" $name]} {
1681 if {![string match "*/HEAD" $name]} {
1682 set headids($name) $id
1683 lappend idheads($id) $name
1685 } elseif {[string match "heads/*" $name]} {
1686 set name [string range $name 6 end]
1687 set headids($name) $id
1688 lappend idheads($id) $name
1689 } elseif {[string match "tags/*" $name]} {
1690 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1691 # which is what we want since the former is the commit ID
1692 set name [string range $name 5 end]
1693 if {[string match "*^{}" $name]} {
1694 set name [string range $name 0 end-3]
1696 set tagobjid($name) $id
1698 set tagids($name) $id
1699 lappend idtags($id) $name
1701 set otherrefids($name) $id
1702 lappend idotherrefs($id) $name
1709 set mainheadid [exec git rev-parse HEAD]
1710 set thehead [exec git symbolic-ref HEAD]
1711 if {[string match "refs/heads/*" $thehead]} {
1712 set mainhead [string range $thehead 11 end]
1716 if {$selecthead ne {}} {
1718 set selectheadid [exec git rev-parse --verify $selecthead]
1723 # skip over fake commits
1724 proc first_real_row {} {
1725 global nullid nullid2 numcommits
1727 for {set row 0} {$row < $numcommits} {incr row} {
1728 set id [commitonrow $row]
1729 if {$id ne $nullid && $id ne $nullid2} {
1736 # update things for a head moved to a child of its previous location
1737 proc movehead {id name} {
1738 global headids idheads
1740 removehead $headids($name) $name
1741 set headids($name) $id
1742 lappend idheads($id) $name
1745 # update things when a head has been removed
1746 proc removehead {id name} {
1747 global headids idheads
1749 if {$idheads($id) eq $name} {
1752 set i [lsearch -exact $idheads($id) $name]
1754 set idheads($id) [lreplace $idheads($id) $i $i]
1757 unset headids($name)
1760 proc make_transient {window origin} {
1763 # In MacOS Tk 8.4 transient appears to work by setting
1764 # overrideredirect, which is utterly useless, since the
1765 # windows get no border, and are not even kept above
1767 if {!$have_tk85 && [tk windowingsystem] eq {aqua}} return
1769 wm transient $window $origin
1771 # Windows fails to place transient windows normally, so
1772 # schedule a callback to center them on the parent.
1773 if {[tk windowingsystem] eq {win32}} {
1774 after idle [list tk::PlaceWindow $window widget $origin]
1778 proc show_error {w top msg} {
1779 message $w.m -text $msg -justify center -aspect 400
1780 pack $w.m -side top -fill x -padx 20 -pady 20
1781 button $w.ok -text [mc OK] -command "destroy $top"
1782 pack $w.ok -side bottom -fill x
1783 bind $top <Visibility> "grab $top; focus $top"
1784 bind $top <Key-Return> "destroy $top"
1785 bind $top <Key-space> "destroy $top"
1786 bind $top <Key-Escape> "destroy $top"
1790 proc error_popup {msg {owner .}} {
1793 make_transient $w $owner
1794 show_error $w $w $msg
1797 proc confirm_popup {msg {owner .}} {
1802 make_transient $w $owner
1803 message $w.m -text $msg -justify center -aspect 400
1804 pack $w.m -side top -fill x -padx 20 -pady 20
1805 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1806 pack $w.ok -side left -fill x
1807 button $w.cancel -text [mc Cancel] -command "destroy $w"
1808 pack $w.cancel -side right -fill x
1809 bind $w <Visibility> "grab $w; focus $w"
1810 bind $w <Key-Return> "set confirm_ok 1; destroy $w"
1811 bind $w <Key-space> "set confirm_ok 1; destroy $w"
1812 bind $w <Key-Escape> "destroy $w"
1817 proc setoptions {} {
1818 option add *Panedwindow.showHandle 1 startupFile
1819 option add *Panedwindow.sashRelief raised startupFile
1820 option add *Button.font uifont startupFile
1821 option add *Checkbutton.font uifont startupFile
1822 option add *Radiobutton.font uifont startupFile
1823 option add *Menu.font uifont startupFile
1824 option add *Menubutton.font uifont startupFile
1825 option add *Label.font uifont startupFile
1826 option add *Message.font uifont startupFile
1827 option add *Entry.font uifont startupFile
1830 # Make a menu and submenus.
1831 # m is the window name for the menu, items is the list of menu items to add.
1832 # Each item is a list {mc label type description options...}
1833 # mc is ignored; it's so we can put mc there to alert xgettext
1834 # label is the string that appears in the menu
1835 # type is cascade, command or radiobutton (should add checkbutton)
1836 # description depends on type; it's the sublist for cascade, the
1837 # command to invoke for command, or {variable value} for radiobutton
1838 proc makemenu {m items} {
1840 if {[tk windowingsystem] eq {aqua}} {
1846 set name [mc [lindex $i 1]]
1847 set type [lindex $i 2]
1848 set thing [lindex $i 3]
1849 set params [list $type]
1851 set u [string first "&" [string map {&& x} $name]]
1852 lappend params -label [string map {&& & & {}} $name]
1854 lappend params -underline $u
1859 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1860 lappend params -menu $m.$submenu
1863 lappend params -command $thing
1866 lappend params -variable [lindex $thing 0] \
1867 -value [lindex $thing 1]
1870 set tail [lrange $i 4 end]
1871 regsub -all {\yMeta1\y} $tail $Meta1 tail
1872 eval $m add $params $tail
1873 if {$type eq "cascade"} {
1874 makemenu $m.$submenu $thing
1879 # translate string and remove ampersands
1881 return [string map {&& & & {}} [mc $str]]
1884 proc makewindow {} {
1885 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1887 global findtype findtypemenu findloc findstring fstring geometry
1888 global entries sha1entry sha1string sha1but
1889 global diffcontextstring diffcontext
1891 global maincursor textcursor curtextcursor
1892 global rowctxmenu fakerowmenu mergemax wrapcomment
1893 global highlight_files gdttype
1894 global searchstring sstring
1895 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1896 global headctxmenu progresscanv progressitem progresscoords statusw
1897 global fprogitem fprogcoord lastprogupdate progupdatepending
1898 global rprogitem rprogcoord rownumsel numcommits
1901 # The "mc" arguments here are purely so that xgettext
1902 # sees the following string as needing to be translated
1904 {mc "File" cascade {
1905 {mc "Update" command updatecommits -accelerator F5}
1906 {mc "Reload" command reloadcommits -accelerator Meta1-F5}
1907 {mc "Reread references" command rereadrefs}
1908 {mc "List references" command showrefs -accelerator F2}
1909 {mc "Quit" command doquit -accelerator Meta1-Q}
1911 {mc "Edit" cascade {
1912 {mc "Preferences" command doprefs}
1914 {mc "View" cascade {
1915 {mc "New view..." command {newview 0} -accelerator Shift-F4}
1916 {mc "Edit view..." command editview -state disabled -accelerator F4}
1917 {mc "Delete view" command delview -state disabled}
1919 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
1921 {mc "Help" cascade {
1922 {mc "About gitk" command about}
1923 {mc "Key bindings" command keys}
1926 . configure -menu .bar
1928 # the gui has upper and lower half, parts of a paned window.
1929 panedwindow .ctop -orient vertical
1931 # possibly use assumed geometry
1932 if {![info exists geometry(pwsash0)]} {
1933 set geometry(topheight) [expr {15 * $linespc}]
1934 set geometry(topwidth) [expr {80 * $charspc}]
1935 set geometry(botheight) [expr {15 * $linespc}]
1936 set geometry(botwidth) [expr {50 * $charspc}]
1937 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1938 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1941 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1942 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1944 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1946 # create three canvases
1947 set cscroll .tf.histframe.csb
1948 set canv .tf.histframe.pwclist.canv
1950 -selectbackground $selectbgcolor \
1951 -background $bgcolor -bd 0 \
1952 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1953 .tf.histframe.pwclist add $canv
1954 set canv2 .tf.histframe.pwclist.canv2
1956 -selectbackground $selectbgcolor \
1957 -background $bgcolor -bd 0 -yscrollincr $linespc
1958 .tf.histframe.pwclist add $canv2
1959 set canv3 .tf.histframe.pwclist.canv3
1961 -selectbackground $selectbgcolor \
1962 -background $bgcolor -bd 0 -yscrollincr $linespc
1963 .tf.histframe.pwclist add $canv3
1964 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1965 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1967 # a scroll bar to rule them
1968 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1969 pack $cscroll -side right -fill y
1970 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1971 lappend bglist $canv $canv2 $canv3
1972 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1974 # we have two button bars at bottom of top frame. Bar 1
1976 frame .tf.lbar -height 15
1978 set sha1entry .tf.bar.sha1
1979 set entries $sha1entry
1980 set sha1but .tf.bar.sha1label
1981 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1982 -command gotocommit -width 8
1983 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1984 pack .tf.bar.sha1label -side left
1985 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1986 trace add variable sha1string write sha1change
1987 pack $sha1entry -side left -pady 2
1989 image create bitmap bm-left -data {
1990 #define left_width 16
1991 #define left_height 16
1992 static unsigned char left_bits[] = {
1993 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1994 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1995 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1997 image create bitmap bm-right -data {
1998 #define right_width 16
1999 #define right_height 16
2000 static unsigned char right_bits[] = {
2001 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
2002 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
2003 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
2005 button .tf.bar.leftbut -image bm-left -command goback \
2006 -state disabled -width 26
2007 pack .tf.bar.leftbut -side left -fill y
2008 button .tf.bar.rightbut -image bm-right -command goforw \
2009 -state disabled -width 26
2010 pack .tf.bar.rightbut -side left -fill y
2012 label .tf.bar.rowlabel -text [mc "Row"]
2014 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
2015 -relief sunken -anchor e
2016 label .tf.bar.rowlabel2 -text "/"
2017 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
2018 -relief sunken -anchor e
2019 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
2022 trace add variable selectedline write selectedline_change
2024 # Status label and progress bar
2025 set statusw .tf.bar.status
2026 label $statusw -width 15 -relief sunken
2027 pack $statusw -side left -padx 5
2028 set h [expr {[font metrics uifont -linespace] + 2}]
2029 set progresscanv .tf.bar.progress
2030 canvas $progresscanv -relief sunken -height $h -borderwidth 2
2031 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
2032 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
2033 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
2034 pack $progresscanv -side right -expand 1 -fill x
2035 set progresscoords {0 0}
2038 bind $progresscanv <Configure> adjustprogress
2039 set lastprogupdate [clock clicks -milliseconds]
2040 set progupdatepending 0
2042 # build up the bottom bar of upper window
2043 label .tf.lbar.flabel -text "[mc "Find"] "
2044 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
2045 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
2046 label .tf.lbar.flab2 -text " [mc "commit"] "
2047 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2049 set gdttype [mc "containing:"]
2050 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
2051 [mc "containing:"] \
2052 [mc "touching paths:"] \
2053 [mc "adding/removing string:"]]
2054 trace add variable gdttype write gdttype_change
2055 pack .tf.lbar.gdttype -side left -fill y
2058 set fstring .tf.lbar.findstring
2059 lappend entries $fstring
2060 entry $fstring -width 30 -font textfont -textvariable findstring
2061 trace add variable findstring write find_change
2062 set findtype [mc "Exact"]
2063 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
2064 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2065 trace add variable findtype write findcom_change
2066 set findloc [mc "All fields"]
2067 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2068 [mc "Comments"] [mc "Author"] [mc "Committer"]
2069 trace add variable findloc write find_change
2070 pack .tf.lbar.findloc -side right
2071 pack .tf.lbar.findtype -side right
2072 pack $fstring -side left -expand 1 -fill x
2074 # Finish putting the upper half of the viewer together
2075 pack .tf.lbar -in .tf -side bottom -fill x
2076 pack .tf.bar -in .tf -side bottom -fill x
2077 pack .tf.histframe -fill both -side top -expand 1
2079 .ctop paneconfigure .tf -height $geometry(topheight)
2080 .ctop paneconfigure .tf -width $geometry(topwidth)
2082 # now build up the bottom
2083 panedwindow .pwbottom -orient horizontal
2085 # lower left, a text box over search bar, scroll bar to the right
2086 # if we know window height, then that will set the lower text height, otherwise
2087 # we set lower text height which will drive window height
2088 if {[info exists geometry(main)]} {
2089 frame .bleft -width $geometry(botwidth)
2091 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2097 button .bleft.top.search -text [mc "Search"] -command dosearch
2098 pack .bleft.top.search -side left -padx 5
2099 set sstring .bleft.top.sstring
2100 entry $sstring -width 20 -font textfont -textvariable searchstring
2101 lappend entries $sstring
2102 trace add variable searchstring write incrsearch
2103 pack $sstring -side left -expand 1 -fill x
2104 radiobutton .bleft.mid.diff -text [mc "Diff"] \
2105 -command changediffdisp -variable diffelide -value {0 0}
2106 radiobutton .bleft.mid.old -text [mc "Old version"] \
2107 -command changediffdisp -variable diffelide -value {0 1}
2108 radiobutton .bleft.mid.new -text [mc "New version"] \
2109 -command changediffdisp -variable diffelide -value {1 0}
2110 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2111 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2112 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
2113 -from 1 -increment 1 -to 10000000 \
2114 -validate all -validatecommand "diffcontextvalidate %P" \
2115 -textvariable diffcontextstring
2116 .bleft.mid.diffcontext set $diffcontext
2117 trace add variable diffcontextstring write diffcontextchange
2118 lappend entries .bleft.mid.diffcontext
2119 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2120 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2121 -command changeignorespace -variable ignorespace
2122 pack .bleft.mid.ignspace -side left -padx 5
2123 set ctext .bleft.bottom.ctext
2124 text $ctext -background $bgcolor -foreground $fgcolor \
2125 -state disabled -font textfont \
2126 -yscrollcommand scrolltext -wrap none \
2127 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2129 $ctext conf -tabstyle wordprocessor
2131 scrollbar .bleft.bottom.sb -command "$ctext yview"
2132 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
2134 pack .bleft.top -side top -fill x
2135 pack .bleft.mid -side top -fill x
2136 grid $ctext .bleft.bottom.sb -sticky nsew
2137 grid .bleft.bottom.sbhorizontal -sticky ew
2138 grid columnconfigure .bleft.bottom 0 -weight 1
2139 grid rowconfigure .bleft.bottom 0 -weight 1
2140 grid rowconfigure .bleft.bottom 1 -weight 0
2141 pack .bleft.bottom -side top -fill both -expand 1
2142 lappend bglist $ctext
2143 lappend fglist $ctext
2145 $ctext tag conf comment -wrap $wrapcomment
2146 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2147 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2148 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2149 $ctext tag conf dresult -fore [lindex $diffcolors 1]
2150 $ctext tag conf m0 -fore red
2151 $ctext tag conf m1 -fore blue
2152 $ctext tag conf m2 -fore green
2153 $ctext tag conf m3 -fore purple
2154 $ctext tag conf m4 -fore brown
2155 $ctext tag conf m5 -fore "#009090"
2156 $ctext tag conf m6 -fore magenta
2157 $ctext tag conf m7 -fore "#808000"
2158 $ctext tag conf m8 -fore "#009000"
2159 $ctext tag conf m9 -fore "#ff0080"
2160 $ctext tag conf m10 -fore cyan
2161 $ctext tag conf m11 -fore "#b07070"
2162 $ctext tag conf m12 -fore "#70b0f0"
2163 $ctext tag conf m13 -fore "#70f0b0"
2164 $ctext tag conf m14 -fore "#f0b070"
2165 $ctext tag conf m15 -fore "#ff70b0"
2166 $ctext tag conf mmax -fore darkgrey
2168 $ctext tag conf mresult -font textfontbold
2169 $ctext tag conf msep -font textfontbold
2170 $ctext tag conf found -back yellow
2172 .pwbottom add .bleft
2173 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2178 radiobutton .bright.mode.patch -text [mc "Patch"] \
2179 -command reselectline -variable cmitmode -value "patch"
2180 radiobutton .bright.mode.tree -text [mc "Tree"] \
2181 -command reselectline -variable cmitmode -value "tree"
2182 grid .bright.mode.patch .bright.mode.tree -sticky ew
2183 pack .bright.mode -side top -fill x
2184 set cflist .bright.cfiles
2185 set indent [font measure mainfont "nn"]
2187 -selectbackground $selectbgcolor \
2188 -background $bgcolor -foreground $fgcolor \
2190 -tabs [list $indent [expr {2 * $indent}]] \
2191 -yscrollcommand ".bright.sb set" \
2192 -cursor [. cget -cursor] \
2193 -spacing1 1 -spacing3 1
2194 lappend bglist $cflist
2195 lappend fglist $cflist
2196 scrollbar .bright.sb -command "$cflist yview"
2197 pack .bright.sb -side right -fill y
2198 pack $cflist -side left -fill both -expand 1
2199 $cflist tag configure highlight \
2200 -background [$cflist cget -selectbackground]
2201 $cflist tag configure bold -font mainfontbold
2203 .pwbottom add .bright
2206 # restore window width & height if known
2207 if {[info exists geometry(main)]} {
2208 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2209 if {$w > [winfo screenwidth .]} {
2210 set w [winfo screenwidth .]
2212 if {$h > [winfo screenheight .]} {
2213 set h [winfo screenheight .]
2215 wm geometry . "${w}x$h"
2219 if {[tk windowingsystem] eq {aqua}} {
2225 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2226 pack .ctop -fill both -expand 1
2227 bindall <1> {selcanvline %W %x %y}
2228 #bindall <B1-Motion> {selcanvline %W %x %y}
2229 if {[tk windowingsystem] == "win32"} {
2230 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2231 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2233 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2234 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2235 if {[tk windowingsystem] eq "aqua"} {
2236 bindall <MouseWheel> {
2237 set delta [expr {- (%D)}]
2238 allcanvs yview scroll $delta units
2242 bindall <2> "canvscan mark %W %x %y"
2243 bindall <B2-Motion> "canvscan dragto %W %x %y"
2244 bindkey <Home> selfirstline
2245 bindkey <End> sellastline
2246 bind . <Key-Up> "selnextline -1"
2247 bind . <Key-Down> "selnextline 1"
2248 bind . <Shift-Key-Up> "dofind -1 0"
2249 bind . <Shift-Key-Down> "dofind 1 0"
2250 bindkey <Key-Right> "goforw"
2251 bindkey <Key-Left> "goback"
2252 bind . <Key-Prior> "selnextpage -1"
2253 bind . <Key-Next> "selnextpage 1"
2254 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2255 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2256 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2257 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2258 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2259 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2260 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2261 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2262 bindkey <Key-space> "$ctext yview scroll 1 pages"
2263 bindkey p "selnextline -1"
2264 bindkey n "selnextline 1"
2267 bindkey i "selnextline -1"
2268 bindkey k "selnextline 1"
2272 bindkey d "$ctext yview scroll 18 units"
2273 bindkey u "$ctext yview scroll -18 units"
2274 bindkey / {dofind 1 1}
2275 bindkey <Key-Return> {dofind 1 1}
2276 bindkey ? {dofind -1 1}
2278 bind . <F5> updatecommits
2279 bind . <$M1B-F5> reloadcommits
2280 bind . <F2> showrefs
2281 bind . <Shift-F4> {newview 0}
2282 catch { bind . <Shift-Key-XF86_Switch_VT_4> {newview 0} }
2283 bind . <F4> edit_or_newview
2284 bind . <$M1B-q> doquit
2285 bind . <$M1B-f> {dofind 1 1}
2286 bind . <$M1B-g> {dofind 1 0}
2287 bind . <$M1B-r> dosearchback
2288 bind . <$M1B-s> dosearch
2289 bind . <$M1B-equal> {incrfont 1}
2290 bind . <$M1B-plus> {incrfont 1}
2291 bind . <$M1B-KP_Add> {incrfont 1}
2292 bind . <$M1B-minus> {incrfont -1}
2293 bind . <$M1B-KP_Subtract> {incrfont -1}
2294 wm protocol . WM_DELETE_WINDOW doquit
2295 bind . <Destroy> {stop_backends}
2296 bind . <Button-1> "click %W"
2297 bind $fstring <Key-Return> {dofind 1 1}
2298 bind $sha1entry <Key-Return> {gotocommit; break}
2299 bind $sha1entry <<PasteSelection>> clearsha1
2300 bind $cflist <1> {sel_flist %W %x %y; break}
2301 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2302 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2304 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2305 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2307 set maincursor [. cget -cursor]
2308 set textcursor [$ctext cget -cursor]
2309 set curtextcursor $textcursor
2311 set rowctxmenu .rowctxmenu
2312 makemenu $rowctxmenu {
2313 {mc "Diff this -> selected" command {diffvssel 0}}
2314 {mc "Diff selected -> this" command {diffvssel 1}}
2315 {mc "Make patch" command mkpatch}
2316 {mc "Create tag" command mktag}
2317 {mc "Write commit to file" command writecommit}
2318 {mc "Create new branch" command mkbranch}
2319 {mc "Cherry-pick this commit" command cherrypick}
2320 {mc "Reset HEAD branch to here" command resethead}
2322 $rowctxmenu configure -tearoff 0
2324 set fakerowmenu .fakerowmenu
2325 makemenu $fakerowmenu {
2326 {mc "Diff this -> selected" command {diffvssel 0}}
2327 {mc "Diff selected -> this" command {diffvssel 1}}
2328 {mc "Make patch" command mkpatch}
2330 $fakerowmenu configure -tearoff 0
2332 set headctxmenu .headctxmenu
2333 makemenu $headctxmenu {
2334 {mc "Check out this branch" command cobranch}
2335 {mc "Remove this branch" command rmbranch}
2337 $headctxmenu configure -tearoff 0
2340 set flist_menu .flistctxmenu
2341 makemenu $flist_menu {
2342 {mc "Highlight this too" command {flist_hl 0}}
2343 {mc "Highlight this only" command {flist_hl 1}}
2344 {mc "External diff" command {external_diff}}
2345 {mc "Blame parent commit" command {external_blame 1}}
2347 $flist_menu configure -tearoff 0
2350 set diff_menu .diffctxmenu
2351 makemenu $diff_menu {
2352 {mc "Show origin of this line" command show_line_source}
2353 {mc "Run git gui blame on this line" command {external_blame_diff}}
2355 $diff_menu configure -tearoff 0
2358 # Windows sends all mouse wheel events to the current focused window, not
2359 # the one where the mouse hovers, so bind those events here and redirect
2360 # to the correct window
2361 proc windows_mousewheel_redirector {W X Y D} {
2362 global canv canv2 canv3
2363 set w [winfo containing -displayof $W $X $Y]
2365 set u [expr {$D < 0 ? 5 : -5}]
2366 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2367 allcanvs yview scroll $u units
2370 $w yview scroll $u units
2376 # Update row number label when selectedline changes
2377 proc selectedline_change {n1 n2 op} {
2378 global selectedline rownumsel
2380 if {$selectedline eq {}} {
2383 set rownumsel [expr {$selectedline + 1}]
2387 # mouse-2 makes all windows scan vertically, but only the one
2388 # the cursor is in scans horizontally
2389 proc canvscan {op w x y} {
2390 global canv canv2 canv3
2391 foreach c [list $canv $canv2 $canv3] {
2400 proc scrollcanv {cscroll f0 f1} {
2401 $cscroll set $f0 $f1
2406 # when we make a key binding for the toplevel, make sure
2407 # it doesn't get triggered when that key is pressed in the
2408 # find string entry widget.
2409 proc bindkey {ev script} {
2412 set escript [bind Entry $ev]
2413 if {$escript == {}} {
2414 set escript [bind Entry <Key>]
2416 foreach e $entries {
2417 bind $e $ev "$escript; break"
2421 # set the focus back to the toplevel for any click outside
2424 global ctext entries
2425 foreach e [concat $entries $ctext] {
2426 if {$w == $e} return
2431 # Adjust the progress bar for a change in requested extent or canvas size
2432 proc adjustprogress {} {
2433 global progresscanv progressitem progresscoords
2434 global fprogitem fprogcoord lastprogupdate progupdatepending
2435 global rprogitem rprogcoord
2437 set w [expr {[winfo width $progresscanv] - 4}]
2438 set x0 [expr {$w * [lindex $progresscoords 0]}]
2439 set x1 [expr {$w * [lindex $progresscoords 1]}]
2440 set h [winfo height $progresscanv]
2441 $progresscanv coords $progressitem $x0 0 $x1 $h
2442 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2443 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2444 set now [clock clicks -milliseconds]
2445 if {$now >= $lastprogupdate + 100} {
2446 set progupdatepending 0
2448 } elseif {!$progupdatepending} {
2449 set progupdatepending 1
2450 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2454 proc doprogupdate {} {
2455 global lastprogupdate progupdatepending
2457 if {$progupdatepending} {
2458 set progupdatepending 0
2459 set lastprogupdate [clock clicks -milliseconds]
2464 proc savestuff {w} {
2465 global canv canv2 canv3 mainfont textfont uifont tabstop
2466 global stuffsaved findmergefiles maxgraphpct
2467 global maxwidth showneartags showlocalchanges
2468 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2469 global cmitmode wrapcomment datetimeformat limitdiffs
2470 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2471 global autoselect extdifftool perfile_attrs markbgcolor
2473 if {$stuffsaved} return
2474 if {![winfo viewable .]} return
2476 set f [open "~/.gitk-new" w]
2477 puts $f [list set mainfont $mainfont]
2478 puts $f [list set textfont $textfont]
2479 puts $f [list set uifont $uifont]
2480 puts $f [list set tabstop $tabstop]
2481 puts $f [list set findmergefiles $findmergefiles]
2482 puts $f [list set maxgraphpct $maxgraphpct]
2483 puts $f [list set maxwidth $maxwidth]
2484 puts $f [list set cmitmode $cmitmode]
2485 puts $f [list set wrapcomment $wrapcomment]
2486 puts $f [list set autoselect $autoselect]
2487 puts $f [list set showneartags $showneartags]
2488 puts $f [list set showlocalchanges $showlocalchanges]
2489 puts $f [list set datetimeformat $datetimeformat]
2490 puts $f [list set limitdiffs $limitdiffs]
2491 puts $f [list set bgcolor $bgcolor]
2492 puts $f [list set fgcolor $fgcolor]
2493 puts $f [list set colors $colors]
2494 puts $f [list set diffcolors $diffcolors]
2495 puts $f [list set markbgcolor $markbgcolor]
2496 puts $f [list set diffcontext $diffcontext]
2497 puts $f [list set selectbgcolor $selectbgcolor]
2498 puts $f [list set extdifftool $extdifftool]
2499 puts $f [list set perfile_attrs $perfile_attrs]
2501 puts $f "set geometry(main) [wm geometry .]"
2502 puts $f "set geometry(topwidth) [winfo width .tf]"
2503 puts $f "set geometry(topheight) [winfo height .tf]"
2504 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2505 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2506 puts $f "set geometry(botwidth) [winfo width .bleft]"
2507 puts $f "set geometry(botheight) [winfo height .bleft]"
2509 puts -nonewline $f "set permviews {"
2510 for {set v 0} {$v < $nextviewnum} {incr v} {
2511 if {$viewperm($v)} {
2512 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2517 file rename -force "~/.gitk-new" "~/.gitk"
2522 proc resizeclistpanes {win w} {
2524 if {[info exists oldwidth($win)]} {
2525 set s0 [$win sash coord 0]
2526 set s1 [$win sash coord 1]
2528 set sash0 [expr {int($w/2 - 2)}]
2529 set sash1 [expr {int($w*5/6 - 2)}]
2531 set factor [expr {1.0 * $w / $oldwidth($win)}]
2532 set sash0 [expr {int($factor * [lindex $s0 0])}]
2533 set sash1 [expr {int($factor * [lindex $s1 0])}]
2537 if {$sash1 < $sash0 + 20} {
2538 set sash1 [expr {$sash0 + 20}]
2540 if {$sash1 > $w - 10} {
2541 set sash1 [expr {$w - 10}]
2542 if {$sash0 > $sash1 - 20} {
2543 set sash0 [expr {$sash1 - 20}]
2547 $win sash place 0 $sash0 [lindex $s0 1]
2548 $win sash place 1 $sash1 [lindex $s1 1]
2550 set oldwidth($win) $w
2553 proc resizecdetpanes {win w} {
2555 if {[info exists oldwidth($win)]} {
2556 set s0 [$win sash coord 0]
2558 set sash0 [expr {int($w*3/4 - 2)}]
2560 set factor [expr {1.0 * $w / $oldwidth($win)}]
2561 set sash0 [expr {int($factor * [lindex $s0 0])}]
2565 if {$sash0 > $w - 15} {
2566 set sash0 [expr {$w - 15}]
2569 $win sash place 0 $sash0 [lindex $s0 1]
2571 set oldwidth($win) $w
2574 proc allcanvs args {
2575 global canv canv2 canv3
2581 proc bindall {event action} {
2582 global canv canv2 canv3
2583 bind $canv $event $action
2584 bind $canv2 $event $action
2585 bind $canv3 $event $action
2591 if {[winfo exists $w]} {
2596 wm title $w [mc "About gitk"]
2598 message $w.m -text [mc "
2599 Gitk - a commit viewer for git
2601 Copyright © 2005-2008 Paul Mackerras
2603 Use and redistribute under the terms of the GNU General Public License"] \
2604 -justify center -aspect 400 -border 2 -bg white -relief groove
2605 pack $w.m -side top -fill x -padx 2 -pady 2
2606 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2607 pack $w.ok -side bottom
2608 bind $w <Visibility> "focus $w.ok"
2609 bind $w <Key-Escape> "destroy $w"
2610 bind $w <Key-Return> "destroy $w"
2615 if {[winfo exists $w]} {
2619 if {[tk windowingsystem] eq {aqua}} {
2625 wm title $w [mc "Gitk key bindings"]
2627 message $w.m -text "
2628 [mc "Gitk key bindings:"]
2630 [mc "<%s-Q> Quit" $M1T]
2631 [mc "<Home> Move to first commit"]
2632 [mc "<End> Move to last commit"]
2633 [mc "<Up>, p, i Move up one commit"]
2634 [mc "<Down>, n, k Move down one commit"]
2635 [mc "<Left>, z, j Go back in history list"]
2636 [mc "<Right>, x, l Go forward in history list"]
2637 [mc "<PageUp> Move up one page in commit list"]
2638 [mc "<PageDown> Move down one page in commit list"]
2639 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2640 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2641 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2642 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2643 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2644 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2645 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2646 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2647 [mc "<Delete>, b Scroll diff view up one page"]
2648 [mc "<Backspace> Scroll diff view up one page"]
2649 [mc "<Space> Scroll diff view down one page"]
2650 [mc "u Scroll diff view up 18 lines"]
2651 [mc "d Scroll diff view down 18 lines"]
2652 [mc "<%s-F> Find" $M1T]
2653 [mc "<%s-G> Move to next find hit" $M1T]
2654 [mc "<Return> Move to next find hit"]
2655 [mc "/ Move to next find hit, or redo find"]
2656 [mc "? Move to previous find hit"]
2657 [mc "f Scroll diff view to next file"]
2658 [mc "<%s-S> Search for next hit in diff view" $M1T]
2659 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2660 [mc "<%s-KP+> Increase font size" $M1T]
2661 [mc "<%s-plus> Increase font size" $M1T]
2662 [mc "<%s-KP-> Decrease font size" $M1T]
2663 [mc "<%s-minus> Decrease font size" $M1T]
2666 -justify left -bg white -border 2 -relief groove
2667 pack $w.m -side top -fill both -padx 2 -pady 2
2668 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2669 bind $w <Key-Escape> [list destroy $w]
2670 pack $w.ok -side bottom
2671 bind $w <Visibility> "focus $w.ok"
2672 bind $w <Key-Escape> "destroy $w"
2673 bind $w <Key-Return> "destroy $w"
2676 # Procedures for manipulating the file list window at the
2677 # bottom right of the overall window.
2679 proc treeview {w l openlevs} {
2680 global treecontents treediropen treeheight treeparent treeindex
2690 set treecontents() {}
2691 $w conf -state normal
2693 while {[string range $f 0 $prefixend] ne $prefix} {
2694 if {$lev <= $openlevs} {
2695 $w mark set e:$treeindex($prefix) "end -1c"
2696 $w mark gravity e:$treeindex($prefix) left
2698 set treeheight($prefix) $ht
2699 incr ht [lindex $htstack end]
2700 set htstack [lreplace $htstack end end]
2701 set prefixend [lindex $prefendstack end]
2702 set prefendstack [lreplace $prefendstack end end]
2703 set prefix [string range $prefix 0 $prefixend]
2706 set tail [string range $f [expr {$prefixend+1}] end]
2707 while {[set slash [string first "/" $tail]] >= 0} {
2710 lappend prefendstack $prefixend
2711 incr prefixend [expr {$slash + 1}]
2712 set d [string range $tail 0 $slash]
2713 lappend treecontents($prefix) $d
2714 set oldprefix $prefix
2716 set treecontents($prefix) {}
2717 set treeindex($prefix) [incr ix]
2718 set treeparent($prefix) $oldprefix
2719 set tail [string range $tail [expr {$slash+1}] end]
2720 if {$lev <= $openlevs} {
2722 set treediropen($prefix) [expr {$lev < $openlevs}]
2723 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2724 $w mark set d:$ix "end -1c"
2725 $w mark gravity d:$ix left
2727 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2729 $w image create end -align center -image $bm -padx 1 \
2731 $w insert end $d [highlight_tag $prefix]
2732 $w mark set s:$ix "end -1c"
2733 $w mark gravity s:$ix left
2738 if {$lev <= $openlevs} {
2741 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2743 $w insert end $tail [highlight_tag $f]
2745 lappend treecontents($prefix) $tail
2748 while {$htstack ne {}} {
2749 set treeheight($prefix) $ht
2750 incr ht [lindex $htstack end]
2751 set htstack [lreplace $htstack end end]
2752 set prefixend [lindex $prefendstack end]
2753 set prefendstack [lreplace $prefendstack end end]
2754 set prefix [string range $prefix 0 $prefixend]
2756 $w conf -state disabled
2759 proc linetoelt {l} {
2760 global treeheight treecontents
2765 foreach e $treecontents($prefix) {
2770 if {[string index $e end] eq "/"} {
2771 set n $treeheight($prefix$e)
2783 proc highlight_tree {y prefix} {
2784 global treeheight treecontents cflist
2786 foreach e $treecontents($prefix) {
2788 if {[highlight_tag $path] ne {}} {
2789 $cflist tag add bold $y.0 "$y.0 lineend"
2792 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2793 set y [highlight_tree $y $path]
2799 proc treeclosedir {w dir} {
2800 global treediropen treeheight treeparent treeindex
2802 set ix $treeindex($dir)
2803 $w conf -state normal
2804 $w delete s:$ix e:$ix
2805 set treediropen($dir) 0
2806 $w image configure a:$ix -image tri-rt
2807 $w conf -state disabled
2808 set n [expr {1 - $treeheight($dir)}]
2809 while {$dir ne {}} {
2810 incr treeheight($dir) $n
2811 set dir $treeparent($dir)
2815 proc treeopendir {w dir} {
2816 global treediropen treeheight treeparent treecontents treeindex
2818 set ix $treeindex($dir)
2819 $w conf -state normal
2820 $w image configure a:$ix -image tri-dn
2821 $w mark set e:$ix s:$ix
2822 $w mark gravity e:$ix right
2825 set n [llength $treecontents($dir)]
2826 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2829 incr treeheight($x) $n
2831 foreach e $treecontents($dir) {
2833 if {[string index $e end] eq "/"} {
2834 set iy $treeindex($de)
2835 $w mark set d:$iy e:$ix
2836 $w mark gravity d:$iy left
2837 $w insert e:$ix $str
2838 set treediropen($de) 0
2839 $w image create e:$ix -align center -image tri-rt -padx 1 \
2841 $w insert e:$ix $e [highlight_tag $de]
2842 $w mark set s:$iy e:$ix
2843 $w mark gravity s:$iy left
2844 set treeheight($de) 1
2846 $w insert e:$ix $str
2847 $w insert e:$ix $e [highlight_tag $de]
2850 $w mark gravity e:$ix right
2851 $w conf -state disabled
2852 set treediropen($dir) 1
2853 set top [lindex [split [$w index @0,0] .] 0]
2854 set ht [$w cget -height]
2855 set l [lindex [split [$w index s:$ix] .] 0]
2858 } elseif {$l + $n + 1 > $top + $ht} {
2859 set top [expr {$l + $n + 2 - $ht}]
2867 proc treeclick {w x y} {
2868 global treediropen cmitmode ctext cflist cflist_top
2870 if {$cmitmode ne "tree"} return
2871 if {![info exists cflist_top]} return
2872 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2873 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2874 $cflist tag add highlight $l.0 "$l.0 lineend"
2880 set e [linetoelt $l]
2881 if {[string index $e end] ne "/"} {
2883 } elseif {$treediropen($e)} {
2890 proc setfilelist {id} {
2891 global treefilelist cflist jump_to_here
2893 treeview $cflist $treefilelist($id) 0
2894 if {$jump_to_here ne {}} {
2895 set f [lindex $jump_to_here 0]
2896 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
2902 image create bitmap tri-rt -background black -foreground blue -data {
2903 #define tri-rt_width 13
2904 #define tri-rt_height 13
2905 static unsigned char tri-rt_bits[] = {
2906 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2907 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2910 #define tri-rt-mask_width 13
2911 #define tri-rt-mask_height 13
2912 static unsigned char tri-rt-mask_bits[] = {
2913 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2914 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2917 image create bitmap tri-dn -background black -foreground blue -data {
2918 #define tri-dn_width 13
2919 #define tri-dn_height 13
2920 static unsigned char tri-dn_bits[] = {
2921 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2922 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2925 #define tri-dn-mask_width 13
2926 #define tri-dn-mask_height 13
2927 static unsigned char tri-dn-mask_bits[] = {
2928 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2929 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2933 image create bitmap reficon-T -background black -foreground yellow -data {
2934 #define tagicon_width 13
2935 #define tagicon_height 9
2936 static unsigned char tagicon_bits[] = {
2937 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2938 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2940 #define tagicon-mask_width 13
2941 #define tagicon-mask_height 9
2942 static unsigned char tagicon-mask_bits[] = {
2943 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2944 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2947 #define headicon_width 13
2948 #define headicon_height 9
2949 static unsigned char headicon_bits[] = {
2950 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2951 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2954 #define headicon-mask_width 13
2955 #define headicon-mask_height 9
2956 static unsigned char headicon-mask_bits[] = {
2957 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2958 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2960 image create bitmap reficon-H -background black -foreground green \
2961 -data $rectdata -maskdata $rectmask
2962 image create bitmap reficon-o -background black -foreground "#ddddff" \
2963 -data $rectdata -maskdata $rectmask
2965 proc init_flist {first} {
2966 global cflist cflist_top difffilestart
2968 $cflist conf -state normal
2969 $cflist delete 0.0 end
2971 $cflist insert end $first
2973 $cflist tag add highlight 1.0 "1.0 lineend"
2975 catch {unset cflist_top}
2977 $cflist conf -state disabled
2978 set difffilestart {}
2981 proc highlight_tag {f} {
2982 global highlight_paths
2984 foreach p $highlight_paths {
2985 if {[string match $p $f]} {
2992 proc highlight_filelist {} {
2993 global cmitmode cflist
2995 $cflist conf -state normal
2996 if {$cmitmode ne "tree"} {
2997 set end [lindex [split [$cflist index end] .] 0]
2998 for {set l 2} {$l < $end} {incr l} {
2999 set line [$cflist get $l.0 "$l.0 lineend"]
3000 if {[highlight_tag $line] ne {}} {
3001 $cflist tag add bold $l.0 "$l.0 lineend"
3007 $cflist conf -state disabled
3010 proc unhighlight_filelist {} {
3013 $cflist conf -state normal
3014 $cflist tag remove bold 1.0 end
3015 $cflist conf -state disabled
3018 proc add_flist {fl} {
3021 $cflist conf -state normal
3023 $cflist insert end "\n"
3024 $cflist insert end $f [highlight_tag $f]
3026 $cflist conf -state disabled
3029 proc sel_flist {w x y} {
3030 global ctext difffilestart cflist cflist_top cmitmode
3032 if {$cmitmode eq "tree"} return
3033 if {![info exists cflist_top]} return
3034 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3035 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3036 $cflist tag add highlight $l.0 "$l.0 lineend"
3041 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
3045 proc pop_flist_menu {w X Y x y} {
3046 global ctext cflist cmitmode flist_menu flist_menu_file
3047 global treediffs diffids
3050 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3052 if {$cmitmode eq "tree"} {
3053 set e [linetoelt $l]
3054 if {[string index $e end] eq "/"} return
3056 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3058 set flist_menu_file $e
3059 set xdiffstate "normal"
3060 if {$cmitmode eq "tree"} {
3061 set xdiffstate "disabled"
3063 # Disable "External diff" item in tree mode
3064 $flist_menu entryconf 2 -state $xdiffstate
3065 tk_popup $flist_menu $X $Y
3068 proc find_ctext_fileinfo {line} {
3069 global ctext_file_names ctext_file_lines
3071 set ok [bsearch $ctext_file_lines $line]
3072 set tline [lindex $ctext_file_lines $ok]
3074 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3077 return [list [lindex $ctext_file_names $ok] $tline]
3081 proc pop_diff_menu {w X Y x y} {
3082 global ctext diff_menu flist_menu_file
3083 global diff_menu_txtpos diff_menu_line
3084 global diff_menu_filebase
3086 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3087 set diff_menu_line [lindex $diff_menu_txtpos 0]
3088 # don't pop up the menu on hunk-separator or file-separator lines
3089 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3093 set f [find_ctext_fileinfo $diff_menu_line]
3094 if {$f eq {}} return
3095 set flist_menu_file [lindex $f 0]
3096 set diff_menu_filebase [lindex $f 1]
3097 tk_popup $diff_menu $X $Y
3100 proc flist_hl {only} {
3101 global flist_menu_file findstring gdttype
3103 set x [shellquote $flist_menu_file]
3104 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3107 append findstring " " $x
3109 set gdttype [mc "touching paths:"]
3112 proc save_file_from_commit {filename output what} {
3115 if {[catch {exec git show $filename -- > $output} err]} {
3116 if {[string match "fatal: bad revision *" $err]} {
3119 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3125 proc external_diff_get_one_file {diffid filename diffdir} {
3126 global nullid nullid2 nullfile
3129 if {$diffid == $nullid} {
3130 set difffile [file join [file dirname $gitdir] $filename]
3131 if {[file exists $difffile]} {
3136 if {$diffid == $nullid2} {
3137 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3138 return [save_file_from_commit :$filename $difffile index]
3140 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3141 return [save_file_from_commit $diffid:$filename $difffile \
3145 proc external_diff {} {
3146 global gitktmpdir nullid nullid2
3147 global flist_menu_file
3150 global gitdir extdifftool
3152 if {[llength $diffids] == 1} {
3153 # no reference commit given
3154 set diffidto [lindex $diffids 0]
3155 if {$diffidto eq $nullid} {
3156 # diffing working copy with index
3157 set diffidfrom $nullid2
3158 } elseif {$diffidto eq $nullid2} {
3159 # diffing index with HEAD
3160 set diffidfrom "HEAD"
3162 # use first parent commit
3163 global parentlist selectedline
3164 set diffidfrom [lindex $parentlist $selectedline 0]
3167 set diffidfrom [lindex $diffids 0]
3168 set diffidto [lindex $diffids 1]
3171 # make sure that several diffs wont collide
3172 if {![info exists gitktmpdir]} {
3173 set gitktmpdir [file join [file dirname $gitdir] \
3174 [format ".gitk-tmp.%s" [pid]]]
3175 if {[catch {file mkdir $gitktmpdir} err]} {
3176 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3183 set diffdir [file join $gitktmpdir $diffnum]
3184 if {[catch {file mkdir $diffdir} err]} {
3185 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3189 # gather files to diff
3190 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3191 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3193 if {$difffromfile ne {} && $difftofile ne {}} {
3194 set cmd [concat | [shellsplit $extdifftool] \
3195 [list $difffromfile $difftofile]]
3196 if {[catch {set fl [open $cmd r]} err]} {
3197 file delete -force $diffdir
3198 error_popup "$extdifftool: [mc "command failed:"] $err"
3200 fconfigure $fl -blocking 0
3201 filerun $fl [list delete_at_eof $fl $diffdir]
3206 proc find_hunk_blamespec {base line} {
3209 # Find and parse the hunk header
3210 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3211 if {$s_lix eq {}} return
3213 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3214 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3215 s_line old_specs osz osz1 new_line nsz]} {
3219 # base lines for the parents
3220 set base_lines [list $new_line]
3221 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3222 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3223 old_spec old_line osz]} {
3226 lappend base_lines $old_line
3229 # Now scan the lines to determine offset within the hunk
3230 set max_parent [expr {[llength $base_lines]-2}]
3232 set s_lno [lindex [split $s_lix "."] 0]
3234 # Determine if the line is removed
3235 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3236 if {[string match {[-+ ]*} $chunk]} {
3237 set removed_idx [string first "-" $chunk]
3238 # Choose a parent index
3239 if {$removed_idx >= 0} {
3240 set parent $removed_idx
3242 set unchanged_idx [string first " " $chunk]
3243 if {$unchanged_idx >= 0} {
3244 set parent $unchanged_idx
3246 # blame the current commit
3250 # then count other lines that belong to it
3251 for {set i $line} {[incr i -1] > $s_lno} {} {
3252 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3253 # Determine if the line is removed
3254 set removed_idx [string first "-" $chunk]
3256 set code [string index $chunk $parent]
3257 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3261 if {$removed_idx < 0} {
3271 incr dline [lindex $base_lines $parent]
3272 return [list $parent $dline]
3275 proc external_blame_diff {} {
3276 global currentid cmitmode
3277 global diff_menu_txtpos diff_menu_line
3278 global diff_menu_filebase flist_menu_file
3280 if {$cmitmode eq "tree"} {
3282 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3284 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3286 set parent_idx [lindex $hinfo 0]
3287 set line [lindex $hinfo 1]
3294 external_blame $parent_idx $line
3297 # Find the SHA1 ID of the blob for file $fname in the index
3299 proc index_sha1 {fname} {
3300 set f [open [list | git ls-files -s $fname] r]
3301 while {[gets $f line] >= 0} {
3302 set info [lindex [split $line "\t"] 0]
3303 set stage [lindex $info 2]
3304 if {$stage eq "0" || $stage eq "2"} {
3306 return [lindex $info 1]
3313 proc external_blame {parent_idx {line {}}} {
3314 global flist_menu_file
3315 global nullid nullid2
3316 global parentlist selectedline currentid
3318 if {$parent_idx > 0} {
3319 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3321 set base_commit $currentid
3324 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3325 error_popup [mc "No such commit"]
3329 set cmdline [list git gui blame]
3330 if {$line ne {} && $line > 1} {
3331 lappend cmdline "--line=$line"
3333 lappend cmdline $base_commit $flist_menu_file
3334 if {[catch {eval exec $cmdline &} err]} {
3335 error_popup "[mc "git gui blame: command failed:"] $err"
3339 proc show_line_source {} {
3340 global cmitmode currentid parents curview blamestuff blameinst
3341 global diff_menu_line diff_menu_filebase flist_menu_file
3342 global nullid nullid2 gitdir
3345 if {$cmitmode eq "tree"} {
3347 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3349 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3350 if {$h eq {}} return
3351 set pi [lindex $h 0]
3353 mark_ctext_line $diff_menu_line
3357 if {$currentid eq $nullid} {
3359 # must be a merge in progress...
3361 # get the last line from .git/MERGE_HEAD
3362 set f [open [file join $gitdir MERGE_HEAD] r]
3363 set id [lindex [split [read $f] "\n"] end-1]
3366 error_popup [mc "Couldn't read merge head: %s" $err]
3369 } elseif {$parents($curview,$currentid) eq $nullid2} {
3370 # need to do the blame from the index
3372 set from_index [index_sha1 $flist_menu_file]
3374 error_popup [mc "Error reading index: %s" $err]
3379 set id [lindex $parents($curview,$currentid) $pi]
3381 set line [lindex $h 1]
3384 if {$from_index ne {}} {
3385 lappend blameargs | git cat-file blob $from_index
3387 lappend blameargs | git blame -p -L$line,+1
3388 if {$from_index ne {}} {
3389 lappend blameargs --contents -
3391 lappend blameargs $id
3393 lappend blameargs -- $flist_menu_file
3395 set f [open $blameargs r]
3397 error_popup [mc "Couldn't start git blame: %s" $err]
3400 fconfigure $f -blocking 0
3401 set i [reg_instance $f]
3402 set blamestuff($i) {}
3404 filerun $f [list read_line_source $f $i]
3407 proc stopblaming {} {
3410 if {[info exists blameinst]} {
3411 stop_instance $blameinst
3416 proc read_line_source {fd inst} {
3417 global blamestuff curview commfd blameinst nullid nullid2
3419 while {[gets $fd line] >= 0} {
3420 lappend blamestuff($inst) $line
3427 fconfigure $fd -blocking 1
3428 if {[catch {close $fd} err]} {
3429 error_popup [mc "Error running git blame: %s" $err]
3434 set line [split [lindex $blamestuff($inst) 0] " "]
3435 set id [lindex $line 0]
3436 set lnum [lindex $line 1]
3437 if {[string length $id] == 40 && [string is xdigit $id] &&
3438 [string is digit -strict $lnum]} {
3439 # look for "filename" line
3440 foreach l $blamestuff($inst) {
3441 if {[string match "filename *" $l]} {
3442 set fname [string range $l 9 end]
3448 # all looks good, select it
3449 if {$id eq $nullid} {
3450 # blame uses all-zeroes to mean not committed,
3451 # which would mean a change in the index
3454 if {[commitinview $id $curview]} {
3455 selectline [rowofcommit $id] 1 [list $fname $lnum]
3457 error_popup [mc "That line comes from commit %s, \
3458 which is not in this view" [shortids $id]]
3461 puts "oops couldn't parse git blame output"
3466 # delete $dir when we see eof on $f (presumably because the child has exited)
3467 proc delete_at_eof {f dir} {
3468 while {[gets $f line] >= 0} {}
3470 if {[catch {close $f} err]} {
3471 error_popup "[mc "External diff viewer failed:"] $err"
3473 file delete -force $dir
3479 # Functions for adding and removing shell-type quoting
3481 proc shellquote {str} {
3482 if {![string match "*\['\"\\ \t]*" $str]} {
3485 if {![string match "*\['\"\\]*" $str]} {
3488 if {![string match "*'*" $str]} {
3491 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3494 proc shellarglist {l} {
3500 append str [shellquote $a]
3505 proc shelldequote {str} {
3510 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3511 append ret [string range $str $used end]
3512 set used [string length $str]
3515 set first [lindex $first 0]
3516 set ch [string index $str $first]
3517 if {$first > $used} {
3518 append ret [string range $str $used [expr {$first - 1}]]
3521 if {$ch eq " " || $ch eq "\t"} break
3524 set first [string first "'" $str $used]
3526 error "unmatched single-quote"
3528 append ret [string range $str $used [expr {$first - 1}]]
3533 if {$used >= [string length $str]} {
3534 error "trailing backslash"
3536 append ret [string index $str $used]
3541 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3542 error "unmatched double-quote"
3544 set first [lindex $first 0]
3545 set ch [string index $str $first]
3546 if {$first > $used} {
3547 append ret [string range $str $used [expr {$first - 1}]]
3550 if {$ch eq "\""} break
3552 append ret [string index $str $used]
3556 return [list $used $ret]
3559 proc shellsplit {str} {
3562 set str [string trimleft $str]
3563 if {$str eq {}} break
3564 set dq [shelldequote $str]
3565 set n [lindex $dq 0]
3566 set word [lindex $dq 1]
3567 set str [string range $str $n end]
3573 # Code to implement multiple views
3575 proc newview {ishighlight} {
3576 global nextviewnum newviewname newishighlight
3577 global revtreeargs viewargscmd newviewopts curview
3579 set newishighlight $ishighlight
3581 if {[winfo exists $top]} {
3585 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3586 set newviewopts($nextviewnum,perm) 0
3587 set newviewopts($nextviewnum,cmd) $viewargscmd($curview)
3588 decode_view_opts $nextviewnum $revtreeargs
3589 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3592 set known_view_options {
3593 {perm b . {} {mc "Remember this view"}}
3594 {args t50= + {} {mc "Commits to include (arguments to git log):"}}
3595 {all b * "--all" {mc "Use all refs"}}
3596 {dorder b . {"--date-order" "-d"} {mc "Strictly sort by date"}}
3597 {lright b . "--left-right" {mc "Mark branch sides"}}
3598 {since t15 + {"--since=*" "--after=*"} {mc "Since date:"}}
3599 {until t15 . {"--until=*" "--before=*"} {mc "Until date:"}}
3600 {limit t10 + "--max-count=*" {mc "Max count:"}}
3601 {skip t10 . "--skip=*" {mc "Skip:"}}
3602 {first b . "--first-parent" {mc "Limit to first parent"}}
3603 {cmd t50= + {} {mc "Command to generate more commits to include:"}}
3606 proc encode_view_opts {n} {
3607 global known_view_options newviewopts
3610 foreach opt $known_view_options {
3611 set patterns [lindex $opt 3]
3612 if {$patterns eq {}} continue
3613 set pattern [lindex $patterns 0]
3615 set val $newviewopts($n,[lindex $opt 0])
3617 if {[lindex $opt 1] eq "b"} {
3619 lappend rargs $pattern
3622 set val [string trim $val]
3624 set pfix [string range $pattern 0 end-1]
3625 lappend rargs $pfix$val
3629 return [concat $rargs [shellsplit $newviewopts($n,args)]]
3632 proc decode_view_opts {n view_args} {
3633 global known_view_options newviewopts
3635 foreach opt $known_view_options {
3636 if {[lindex $opt 1] eq "b"} {
3641 set newviewopts($n,[lindex $opt 0]) $val
3644 foreach arg $view_args {
3645 if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3646 && ![info exists found(limit)]} {
3647 set newviewopts($n,limit) $cnt
3652 foreach opt $known_view_options {
3653 set id [lindex $opt 0]
3654 if {[info exists found($id)]} continue
3655 foreach pattern [lindex $opt 3] {
3656 if {![string match $pattern $arg]} continue
3657 if {[lindex $opt 1] ne "b"} {
3658 set size [string length $pattern]
3659 set val [string range $arg [expr {$size-1}] end]
3663 set newviewopts($n,$id) $val
3667 if {[info exists val]} break
3669 if {[info exists val]} continue
3672 set newviewopts($n,args) [shellarglist $oargs]
3675 proc edit_or_newview {} {
3687 global viewname viewperm newviewname newviewopts
3688 global viewargs viewargscmd
3690 set top .gitkvedit-$curview
3691 if {[winfo exists $top]} {
3695 set newviewname($curview) $viewname($curview)
3696 set newviewopts($curview,perm) $viewperm($curview)
3697 set newviewopts($curview,cmd) $viewargscmd($curview)
3698 decode_view_opts $curview $viewargs($curview)
3699 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3702 proc vieweditor {top n title} {
3703 global newviewname newviewopts viewfiles bgcolor
3704 global known_view_options
3707 wm title $top $title
3708 make_transient $top .
3712 label $top.nl -text [mc "Name"]
3713 entry $top.name -width 20 -textvariable newviewname($n)
3714 pack $top.nfr -in $top -fill x -pady 5 -padx 3
3715 pack $top.nl -in $top.nfr -side left -padx {0 30}
3716 pack $top.name -in $top.nfr -side left
3722 foreach opt $known_view_options {
3723 set id [lindex $opt 0]
3724 set type [lindex $opt 1]
3725 set flags [lindex $opt 2]
3726 set title [eval [lindex $opt 4]]
3729 if {$flags eq "+" || $flags eq "*"} {
3730 set cframe $top.fr$cnt
3733 pack $cframe -in $top -fill x -pady 3 -padx 3
3734 set cexpand [expr {$flags eq "*"}]
3740 checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
3741 pack $cframe.c_$id -in $cframe -side left \
3742 -padx [list $lxpad 0] -expand $cexpand -anchor w
3743 } elseif {[regexp {^t(\d+)$} $type type sz]} {
3744 message $cframe.l_$id -aspect 1500 -text $title
3745 entry $cframe.e_$id -width $sz -background $bgcolor \
3746 -textvariable newviewopts($n,$id)
3747 pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
3748 pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
3749 } elseif {[regexp {^t(\d+)=$} $type type sz]} {
3750 message $cframe.l_$id -aspect 1500 -text $title
3751 entry $cframe.e_$id -width $sz -background $bgcolor \
3752 -textvariable newviewopts($n,$id)
3753 pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
3754 pack $cframe.e_$id -in $cframe -side top -fill x
3759 message $top.l -aspect 1500 \
3760 -text [mc "Enter files and directories to include, one per line:"]
3761 pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3
3762 text $top.t -width 40 -height 5 -background $bgcolor -font uifont
3763 if {[info exists viewfiles($n)]} {
3764 foreach f $viewfiles($n) {
3765 $top.t insert end $f
3766 $top.t insert end "\n"
3768 $top.t delete {end - 1c} end
3769 $top.t mark set insert 0.0
3771 pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
3773 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3774 button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
3775 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3776 bind $top <Control-Return> [list newviewok $top $n]
3777 bind $top <F5> [list newviewok $top $n 1]
3778 bind $top <Escape> [list destroy $top]
3779 grid $top.buts.ok $top.buts.apply $top.buts.can
3780 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3781 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3782 grid columnconfigure $top.buts 2 -weight 1 -uniform a
3783 pack $top.buts -in $top -side top -fill x
3787 proc doviewmenu {m first cmd op argv} {
3788 set nmenu [$m index end]
3789 for {set i $first} {$i <= $nmenu} {incr i} {
3790 if {[$m entrycget $i -command] eq $cmd} {
3791 eval $m $op $i $argv
3797 proc allviewmenus {n op args} {
3800 doviewmenu .bar.view 5 [list showview $n] $op $args
3801 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3804 proc newviewok {top n {apply 0}} {
3805 global nextviewnum newviewperm newviewname newishighlight
3806 global viewname viewfiles viewperm selectedview curview
3807 global viewargs viewargscmd newviewopts viewhlmenu
3810 set newargs [encode_view_opts $n]
3812 error_popup "[mc "Error in commit selection arguments:"] $err" $top
3816 foreach f [split [$top.t get 0.0 end] "\n"] {
3817 set ft [string trim $f]
3822 if {![info exists viewfiles($n)]} {
3823 # creating a new view
3825 set viewname($n) $newviewname($n)
3826 set viewperm($n) $newviewopts($n,perm)
3827 set viewfiles($n) $files
3828 set viewargs($n) $newargs
3829 set viewargscmd($n) $newviewopts($n,cmd)
3831 if {!$newishighlight} {
3834 run addvhighlight $n
3837 # editing an existing view
3838 set viewperm($n) $newviewopts($n,perm)
3839 if {$newviewname($n) ne $viewname($n)} {
3840 set viewname($n) $newviewname($n)
3841 doviewmenu .bar.view 5 [list showview $n] \
3842 entryconf [list -label $viewname($n)]
3843 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3844 # entryconf [list -label $viewname($n) -value $viewname($n)]
3846 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3847 $newviewopts($n,cmd) ne $viewargscmd($n)} {
3848 set viewfiles($n) $files
3849 set viewargs($n) $newargs
3850 set viewargscmd($n) $newviewopts($n,cmd)
3851 if {$curview == $n} {
3857 catch {destroy $top}
3861 global curview viewperm hlview selectedhlview
3863 if {$curview == 0} return
3864 if {[info exists hlview] && $hlview == $curview} {
3865 set selectedhlview [mc "None"]
3868 allviewmenus $curview delete
3869 set viewperm($curview) 0
3873 proc addviewmenu {n} {
3874 global viewname viewhlmenu
3876 .bar.view add radiobutton -label $viewname($n) \
3877 -command [list showview $n] -variable selectedview -value $n
3878 #$viewhlmenu add radiobutton -label $viewname($n) \
3879 # -command [list addvhighlight $n] -variable selectedhlview
3883 global curview cached_commitrow ordertok
3884 global displayorder parentlist rowidlist rowisopt rowfinal
3885 global colormap rowtextx nextcolor canvxmax
3886 global numcommits viewcomplete
3887 global selectedline currentid canv canvy0
3889 global pending_select mainheadid
3892 global hlview selectedhlview commitinterest
3894 if {$n == $curview} return
3896 set ymax [lindex [$canv cget -scrollregion] 3]
3897 set span [$canv yview]
3898 set ytop [expr {[lindex $span 0] * $ymax}]
3899 set ybot [expr {[lindex $span 1] * $ymax}]
3900 set yscreen [expr {($ybot - $ytop) / 2}]
3901 if {$selectedline ne {}} {
3902 set selid $currentid
3903 set y [yc $selectedline]
3904 if {$ytop < $y && $y < $ybot} {
3905 set yscreen [expr {$y - $ytop}]
3907 } elseif {[info exists pending_select]} {
3908 set selid $pending_select
3909 unset pending_select
3913 catch {unset treediffs}
3915 if {[info exists hlview] && $hlview == $n} {
3917 set selectedhlview [mc "None"]
3919 catch {unset commitinterest}
3920 catch {unset cached_commitrow}
3921 catch {unset ordertok}
3925 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3926 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3929 if {![info exists viewcomplete($n)]} {
3939 set numcommits $commitidx($n)
3941 catch {unset colormap}
3942 catch {unset rowtextx}
3944 set canvxmax [$canv cget -width]
3950 if {$selid ne {} && [commitinview $selid $n]} {
3951 set row [rowofcommit $selid]
3952 # try to get the selected row in the same position on the screen
3953 set ymax [lindex [$canv cget -scrollregion] 3]
3954 set ytop [expr {[yc $row] - $yscreen}]
3958 set yf [expr {$ytop * 1.0 / $ymax}]
3960 allcanvs yview moveto $yf
3964 } elseif {!$viewcomplete($n)} {
3965 reset_pending_select $selid
3967 reset_pending_select {}
3969 if {[commitinview $pending_select $curview]} {
3970 selectline [rowofcommit $pending_select] 1
3972 set row [first_real_row]
3973 if {$row < $numcommits} {
3978 if {!$viewcomplete($n)} {
3979 if {$numcommits == 0} {
3980 show_status [mc "Reading commits..."]
3982 } elseif {$numcommits == 0} {
3983 show_status [mc "No commits selected"]
3987 # Stuff relating to the highlighting facility
3989 proc ishighlighted {id} {
3990 global vhighlights fhighlights nhighlights rhighlights
3992 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3993 return $nhighlights($id)
3995 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3996 return $vhighlights($id)
3998 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3999 return $fhighlights($id)
4001 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
4002 return $rhighlights($id)
4007 proc bolden {row font} {
4008 global canv linehtag selectedline boldrows need_redisplay
4010 # need_redisplay = 1 means the display is stale and about to be redrawn
4011 if {$need_redisplay} return
4012 lappend boldrows $row
4013 $canv itemconf $linehtag($row) -font $font
4014 if {$row == $selectedline} {
4016 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
4017 -outline {{}} -tags secsel \
4018 -fill [$canv cget -selectbackground]]
4023 proc bolden_name {row font} {
4024 global canv2 linentag selectedline boldnamerows need_redisplay
4026 if {$need_redisplay} return
4027 lappend boldnamerows $row
4028 $canv2 itemconf $linentag($row) -font $font
4029 if {$row == $selectedline} {
4030 $canv2 delete secsel
4031 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
4032 -outline {{}} -tags secsel \
4033 -fill [$canv2 cget -selectbackground]]
4042 foreach row $boldrows {
4043 if {![ishighlighted [commitonrow $row]]} {
4044 bolden $row mainfont
4046 lappend stillbold $row
4049 set boldrows $stillbold
4052 proc addvhighlight {n} {
4053 global hlview viewcomplete curview vhl_done commitidx
4055 if {[info exists hlview]} {
4059 if {$n != $curview && ![info exists viewcomplete($n)]} {
4062 set vhl_done $commitidx($hlview)
4063 if {$vhl_done > 0} {
4068 proc delvhighlight {} {
4069 global hlview vhighlights
4071 if {![info exists hlview]} return
4073 catch {unset vhighlights}
4077 proc vhighlightmore {} {
4078 global hlview vhl_done commitidx vhighlights curview
4080 set max $commitidx($hlview)
4081 set vr [visiblerows]
4082 set r0 [lindex $vr 0]
4083 set r1 [lindex $vr 1]
4084 for {set i $vhl_done} {$i < $max} {incr i} {
4085 set id [commitonrow $i $hlview]
4086 if {[commitinview $id $curview]} {
4087 set row [rowofcommit $id]
4088 if {$r0 <= $row && $row <= $r1} {
4089 if {![highlighted $row]} {
4090 bolden $row mainfontbold
4092 set vhighlights($id) 1
4100 proc askvhighlight {row id} {
4101 global hlview vhighlights iddrawn
4103 if {[commitinview $id $hlview]} {
4104 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
4105 bolden $row mainfontbold
4107 set vhighlights($id) 1
4109 set vhighlights($id) 0
4113 proc hfiles_change {} {
4114 global highlight_files filehighlight fhighlights fh_serial
4115 global highlight_paths gdttype
4117 if {[info exists filehighlight]} {
4118 # delete previous highlights
4119 catch {close $filehighlight}
4121 catch {unset fhighlights}
4123 unhighlight_filelist
4125 set highlight_paths {}
4126 after cancel do_file_hl $fh_serial
4128 if {$highlight_files ne {}} {
4129 after 300 do_file_hl $fh_serial
4133 proc gdttype_change {name ix op} {
4134 global gdttype highlight_files findstring findpattern
4137 if {$findstring ne {}} {
4138 if {$gdttype eq [mc "containing:"]} {
4139 if {$highlight_files ne {}} {
4140 set highlight_files {}
4145 if {$findpattern ne {}} {
4149 set highlight_files $findstring
4154 # enable/disable findtype/findloc menus too
4157 proc find_change {name ix op} {
4158 global gdttype findstring highlight_files
4161 if {$gdttype eq [mc "containing:"]} {
4164 if {$highlight_files ne $findstring} {
4165 set highlight_files $findstring
4172 proc findcom_change args {
4173 global nhighlights boldnamerows
4174 global findpattern findtype findstring gdttype
4177 # delete previous highlights, if any
4178 foreach row $boldnamerows {
4179 bolden_name $row mainfont
4182 catch {unset nhighlights}
4185 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4187 } elseif {$findtype eq [mc "Regexp"]} {
4188 set findpattern $findstring
4190 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4192 set findpattern "*$e*"
4196 proc makepatterns {l} {
4199 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4200 if {[string index $ee end] eq "/"} {
4210 proc do_file_hl {serial} {
4211 global highlight_files filehighlight highlight_paths gdttype fhl_list
4213 if {$gdttype eq [mc "touching paths:"]} {
4214 if {[catch {set paths [shellsplit $highlight_files]}]} return
4215 set highlight_paths [makepatterns $paths]
4217 set gdtargs [concat -- $paths]
4218 } elseif {$gdttype eq [mc "adding/removing string:"]} {
4219 set gdtargs [list "-S$highlight_files"]
4221 # must be "containing:", i.e. we're searching commit info
4224 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4225 set filehighlight [open $cmd r+]
4226 fconfigure $filehighlight -blocking 0
4227 filerun $filehighlight readfhighlight
4233 proc flushhighlights {} {
4234 global filehighlight fhl_list
4236 if {[info exists filehighlight]} {
4238 puts $filehighlight ""
4239 flush $filehighlight
4243 proc askfilehighlight {row id} {
4244 global filehighlight fhighlights fhl_list
4246 lappend fhl_list $id
4247 set fhighlights($id) -1
4248 puts $filehighlight $id
4251 proc readfhighlight {} {
4252 global filehighlight fhighlights curview iddrawn
4253 global fhl_list find_dirn
4255 if {![info exists filehighlight]} {
4259 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4260 set line [string trim $line]
4261 set i [lsearch -exact $fhl_list $line]
4262 if {$i < 0} continue
4263 for {set j 0} {$j < $i} {incr j} {
4264 set id [lindex $fhl_list $j]
4265 set fhighlights($id) 0
4267 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4268 if {$line eq {}} continue
4269 if {![commitinview $line $curview]} continue
4270 set row [rowofcommit $line]
4271 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4272 bolden $row mainfontbold
4274 set fhighlights($line) 1
4276 if {[eof $filehighlight]} {
4278 puts "oops, git diff-tree died"
4279 catch {close $filehighlight}
4283 if {[info exists find_dirn]} {
4289 proc doesmatch {f} {
4290 global findtype findpattern
4292 if {$findtype eq [mc "Regexp"]} {
4293 return [regexp $findpattern $f]
4294 } elseif {$findtype eq [mc "IgnCase"]} {
4295 return [string match -nocase $findpattern $f]
4297 return [string match $findpattern $f]
4301 proc askfindhighlight {row id} {
4302 global nhighlights commitinfo iddrawn
4304 global markingmatches
4306 if {![info exists commitinfo($id)]} {
4309 set info $commitinfo($id)
4311 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4312 foreach f $info ty $fldtypes {
4313 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4315 if {$ty eq [mc "Author"]} {
4322 if {$isbold && [info exists iddrawn($id)]} {
4323 if {![ishighlighted $id]} {
4324 bolden $row mainfontbold
4326 bolden_name $row mainfontbold
4329 if {$markingmatches} {
4330 markrowmatches $row $id
4333 set nhighlights($id) $isbold
4336 proc markrowmatches {row id} {
4337 global canv canv2 linehtag linentag commitinfo findloc
4339 set headline [lindex $commitinfo($id) 0]
4340 set author [lindex $commitinfo($id) 1]
4341 $canv delete match$row
4342 $canv2 delete match$row
4343 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4344 set m [findmatches $headline]
4346 markmatches $canv $row $headline $linehtag($row) $m \
4347 [$canv itemcget $linehtag($row) -font] $row
4350 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4351 set m [findmatches $author]
4353 markmatches $canv2 $row $author $linentag($row) $m \
4354 [$canv2 itemcget $linentag($row) -font] $row
4359 proc vrel_change {name ix op} {
4360 global highlight_related
4363 if {$highlight_related ne [mc "None"]} {
4368 # prepare for testing whether commits are descendents or ancestors of a
4369 proc rhighlight_sel {a} {
4370 global descendent desc_todo ancestor anc_todo
4371 global highlight_related
4373 catch {unset descendent}
4374 set desc_todo [list $a]
4375 catch {unset ancestor}
4376 set anc_todo [list $a]
4377 if {$highlight_related ne [mc "None"]} {
4383 proc rhighlight_none {} {
4386 catch {unset rhighlights}
4390 proc is_descendent {a} {
4391 global curview children descendent desc_todo
4394 set la [rowofcommit $a]
4398 for {set i 0} {$i < [llength $todo]} {incr i} {
4399 set do [lindex $todo $i]
4400 if {[rowofcommit $do] < $la} {
4401 lappend leftover $do
4404 foreach nk $children($v,$do) {
4405 if {![info exists descendent($nk)]} {
4406 set descendent($nk) 1
4414 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4418 set descendent($a) 0
4419 set desc_todo $leftover
4422 proc is_ancestor {a} {
4423 global curview parents ancestor anc_todo
4426 set la [rowofcommit $a]
4430 for {set i 0} {$i < [llength $todo]} {incr i} {
4431 set do [lindex $todo $i]
4432 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4433 lappend leftover $do
4436 foreach np $parents($v,$do) {
4437 if {![info exists ancestor($np)]} {
4446 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4451 set anc_todo $leftover
4454 proc askrelhighlight {row id} {
4455 global descendent highlight_related iddrawn rhighlights
4456 global selectedline ancestor
4458 if {$selectedline eq {}} return
4460 if {$highlight_related eq [mc "Descendant"] ||
4461 $highlight_related eq [mc "Not descendant"]} {
4462 if {![info exists descendent($id)]} {
4465 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4468 } elseif {$highlight_related eq [mc "Ancestor"] ||
4469 $highlight_related eq [mc "Not ancestor"]} {
4470 if {![info exists ancestor($id)]} {
4473 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4477 if {[info exists iddrawn($id)]} {
4478 if {$isbold && ![ishighlighted $id]} {
4479 bolden $row mainfontbold
4482 set rhighlights($id) $isbold
4485 # Graph layout functions
4487 proc shortids {ids} {
4490 if {[llength $id] > 1} {
4491 lappend res [shortids $id]
4492 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4493 lappend res [string range $id 0 7]
4504 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4505 if {($n & $mask) != 0} {
4506 set ret [concat $ret $o]
4508 set o [concat $o $o]
4513 proc ordertoken {id} {
4514 global ordertok curview varcid varcstart varctok curview parents children
4515 global nullid nullid2
4517 if {[info exists ordertok($id)]} {
4518 return $ordertok($id)
4523 if {[info exists varcid($curview,$id)]} {
4524 set a $varcid($curview,$id)
4525 set p [lindex $varcstart($curview) $a]
4527 set p [lindex $children($curview,$id) 0]
4529 if {[info exists ordertok($p)]} {
4530 set tok $ordertok($p)
4533 set id [first_real_child $curview,$p]
4536 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4539 if {[llength $parents($curview,$id)] == 1} {
4540 lappend todo [list $p {}]
4542 set j [lsearch -exact $parents($curview,$id) $p]
4544 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4546 lappend todo [list $p [strrep $j]]
4549 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4550 set p [lindex $todo $i 0]
4551 append tok [lindex $todo $i 1]
4552 set ordertok($p) $tok
4554 set ordertok($origid) $tok
4558 # Work out where id should go in idlist so that order-token
4559 # values increase from left to right
4560 proc idcol {idlist id {i 0}} {
4561 set t [ordertoken $id]
4565 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4566 if {$i > [llength $idlist]} {
4567 set i [llength $idlist]
4569 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4572 if {$t > [ordertoken [lindex $idlist $i]]} {
4573 while {[incr i] < [llength $idlist] &&
4574 $t >= [ordertoken [lindex $idlist $i]]} {}
4580 proc initlayout {} {
4581 global rowidlist rowisopt rowfinal displayorder parentlist
4582 global numcommits canvxmax canv
4584 global colormap rowtextx
4593 set canvxmax [$canv cget -width]
4594 catch {unset colormap}
4595 catch {unset rowtextx}
4599 proc setcanvscroll {} {
4600 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4601 global lastscrollset lastscrollrows
4603 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4604 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4605 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4606 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4607 set lastscrollset [clock clicks -milliseconds]
4608 set lastscrollrows $numcommits
4611 proc visiblerows {} {
4612 global canv numcommits linespc
4614 set ymax [lindex [$canv cget -scrollregion] 3]
4615 if {$ymax eq {} || $ymax == 0} return
4617 set y0 [expr {int([lindex $f 0] * $ymax)}]
4618 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4622 set y1 [expr {int([lindex $f 1] * $ymax)}]
4623 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4624 if {$r1 >= $numcommits} {
4625 set r1 [expr {$numcommits - 1}]
4627 return [list $r0 $r1]
4630 proc layoutmore {} {
4631 global commitidx viewcomplete curview
4632 global numcommits pending_select curview
4633 global lastscrollset lastscrollrows
4635 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4636 [clock clicks -milliseconds] - $lastscrollset > 500} {
4639 if {[info exists pending_select] &&
4640 [commitinview $pending_select $curview]} {
4642 selectline [rowofcommit $pending_select] 1
4647 proc doshowlocalchanges {} {
4648 global curview mainheadid
4650 if {$mainheadid eq {}} return
4651 if {[commitinview $mainheadid $curview]} {
4654 interestedin $mainheadid dodiffindex
4658 proc dohidelocalchanges {} {
4659 global nullid nullid2 lserial curview
4661 if {[commitinview $nullid $curview]} {
4662 removefakerow $nullid
4664 if {[commitinview $nullid2 $curview]} {
4665 removefakerow $nullid2
4670 # spawn off a process to do git diff-index --cached HEAD
4671 proc dodiffindex {} {
4672 global lserial showlocalchanges
4675 if {!$showlocalchanges || !$isworktree} return
4677 set fd [open "|git diff-index --cached HEAD" r]
4678 fconfigure $fd -blocking 0
4679 set i [reg_instance $fd]
4680 filerun $fd [list readdiffindex $fd $lserial $i]
4683 proc readdiffindex {fd serial inst} {
4684 global mainheadid nullid nullid2 curview 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 # now see if there are any local changes not checked in to the index
4701 set fd [open "|git diff-files" r]
4702 fconfigure $fd -blocking 0
4703 set i [reg_instance $fd]
4704 filerun $fd [list readdifffiles $fd $serial $i]
4706 if {$isdiff && ![commitinview $nullid2 $curview]} {
4707 # add the line for the changes in the index to the graph
4708 set hl [mc "Local changes checked in to index but not committed"]
4709 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4710 set commitdata($nullid2) "\n $hl\n"
4711 if {[commitinview $nullid $curview]} {
4712 removefakerow $nullid
4714 insertfakerow $nullid2 $mainheadid
4715 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4716 removefakerow $nullid2
4721 proc readdifffiles {fd serial inst} {
4722 global mainheadid nullid nullid2 curview
4723 global commitinfo commitdata lserial
4726 if {[gets $fd line] < 0} {
4732 # we only need to see one line and we don't really care what it says...
4735 if {$serial != $lserial} {
4739 if {$isdiff && ![commitinview $nullid $curview]} {
4740 # add the line for the local diff to the graph
4741 set hl [mc "Local uncommitted changes, not checked in to index"]
4742 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4743 set commitdata($nullid) "\n $hl\n"
4744 if {[commitinview $nullid2 $curview]} {
4749 insertfakerow $nullid $p
4750 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4751 removefakerow $nullid
4756 proc nextuse {id row} {
4757 global curview children
4759 if {[info exists children($curview,$id)]} {
4760 foreach kid $children($curview,$id) {
4761 if {![commitinview $kid $curview]} {
4764 if {[rowofcommit $kid] > $row} {
4765 return [rowofcommit $kid]
4769 if {[commitinview $id $curview]} {
4770 return [rowofcommit $id]
4775 proc prevuse {id row} {
4776 global curview children
4779 if {[info exists children($curview,$id)]} {
4780 foreach kid $children($curview,$id) {
4781 if {![commitinview $kid $curview]} break
4782 if {[rowofcommit $kid] < $row} {
4783 set ret [rowofcommit $kid]
4790 proc make_idlist {row} {
4791 global displayorder parentlist uparrowlen downarrowlen mingaplen
4792 global commitidx curview children
4794 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4798 set ra [expr {$row - $downarrowlen}]
4802 set rb [expr {$row + $uparrowlen}]
4803 if {$rb > $commitidx($curview)} {
4804 set rb $commitidx($curview)
4806 make_disporder $r [expr {$rb + 1}]
4808 for {} {$r < $ra} {incr r} {
4809 set nextid [lindex $displayorder [expr {$r + 1}]]
4810 foreach p [lindex $parentlist $r] {
4811 if {$p eq $nextid} continue
4812 set rn [nextuse $p $r]
4814 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4815 lappend ids [list [ordertoken $p] $p]
4819 for {} {$r < $row} {incr r} {
4820 set nextid [lindex $displayorder [expr {$r + 1}]]
4821 foreach p [lindex $parentlist $r] {
4822 if {$p eq $nextid} continue
4823 set rn [nextuse $p $r]
4824 if {$rn < 0 || $rn >= $row} {
4825 lappend ids [list [ordertoken $p] $p]
4829 set id [lindex $displayorder $row]
4830 lappend ids [list [ordertoken $id] $id]
4832 foreach p [lindex $parentlist $r] {
4833 set firstkid [lindex $children($curview,$p) 0]
4834 if {[rowofcommit $firstkid] < $row} {
4835 lappend ids [list [ordertoken $p] $p]
4839 set id [lindex $displayorder $r]
4841 set firstkid [lindex $children($curview,$id) 0]
4842 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4843 lappend ids [list [ordertoken $id] $id]
4848 foreach idx [lsort -unique $ids] {
4849 lappend idlist [lindex $idx 1]
4854 proc rowsequal {a b} {
4855 while {[set i [lsearch -exact $a {}]] >= 0} {
4856 set a [lreplace $a $i $i]
4858 while {[set i [lsearch -exact $b {}]] >= 0} {
4859 set b [lreplace $b $i $i]
4861 return [expr {$a eq $b}]
4864 proc makeupline {id row rend col} {
4865 global rowidlist uparrowlen downarrowlen mingaplen
4867 for {set r $rend} {1} {set r $rstart} {
4868 set rstart [prevuse $id $r]
4869 if {$rstart < 0} return
4870 if {$rstart < $row} break
4872 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4873 set rstart [expr {$rend - $uparrowlen - 1}]
4875 for {set r $rstart} {[incr r] <= $row} {} {
4876 set idlist [lindex $rowidlist $r]
4877 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4878 set col [idcol $idlist $id $col]
4879 lset rowidlist $r [linsert $idlist $col $id]
4885 proc layoutrows {row endrow} {
4886 global rowidlist rowisopt rowfinal displayorder
4887 global uparrowlen downarrowlen maxwidth mingaplen
4888 global children parentlist
4889 global commitidx viewcomplete curview
4891 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4894 set rm1 [expr {$row - 1}]
4895 foreach id [lindex $rowidlist $rm1] {
4900 set final [lindex $rowfinal $rm1]
4902 for {} {$row < $endrow} {incr row} {
4903 set rm1 [expr {$row - 1}]
4904 if {$rm1 < 0 || $idlist eq {}} {
4905 set idlist [make_idlist $row]
4908 set id [lindex $displayorder $rm1]
4909 set col [lsearch -exact $idlist $id]
4910 set idlist [lreplace $idlist $col $col]
4911 foreach p [lindex $parentlist $rm1] {
4912 if {[lsearch -exact $idlist $p] < 0} {
4913 set col [idcol $idlist $p $col]
4914 set idlist [linsert $idlist $col $p]
4915 # if not the first child, we have to insert a line going up
4916 if {$id ne [lindex $children($curview,$p) 0]} {
4917 makeupline $p $rm1 $row $col
4921 set id [lindex $displayorder $row]
4922 if {$row > $downarrowlen} {
4923 set termrow [expr {$row - $downarrowlen - 1}]
4924 foreach p [lindex $parentlist $termrow] {
4925 set i [lsearch -exact $idlist $p]
4926 if {$i < 0} continue
4927 set nr [nextuse $p $termrow]
4928 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4929 set idlist [lreplace $idlist $i $i]
4933 set col [lsearch -exact $idlist $id]
4935 set col [idcol $idlist $id]
4936 set idlist [linsert $idlist $col $id]
4937 if {$children($curview,$id) ne {}} {
4938 makeupline $id $rm1 $row $col
4941 set r [expr {$row + $uparrowlen - 1}]
4942 if {$r < $commitidx($curview)} {
4944 foreach p [lindex $parentlist $r] {
4945 if {[lsearch -exact $idlist $p] >= 0} continue
4946 set fk [lindex $children($curview,$p) 0]
4947 if {[rowofcommit $fk] < $row} {
4948 set x [idcol $idlist $p $x]
4949 set idlist [linsert $idlist $x $p]
4952 if {[incr r] < $commitidx($curview)} {
4953 set p [lindex $displayorder $r]
4954 if {[lsearch -exact $idlist $p] < 0} {
4955 set fk [lindex $children($curview,$p) 0]
4956 if {$fk ne {} && [rowofcommit $fk] < $row} {
4957 set x [idcol $idlist $p $x]
4958 set idlist [linsert $idlist $x $p]
4964 if {$final && !$viewcomplete($curview) &&
4965 $row + $uparrowlen + $mingaplen + $downarrowlen
4966 >= $commitidx($curview)} {
4969 set l [llength $rowidlist]
4971 lappend rowidlist $idlist
4973 lappend rowfinal $final
4974 } elseif {$row < $l} {
4975 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4976 lset rowidlist $row $idlist
4979 lset rowfinal $row $final
4981 set pad [ntimes [expr {$row - $l}] {}]
4982 set rowidlist [concat $rowidlist $pad]
4983 lappend rowidlist $idlist
4984 set rowfinal [concat $rowfinal $pad]
4985 lappend rowfinal $final
4986 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4992 proc changedrow {row} {
4993 global displayorder iddrawn rowisopt need_redisplay
4995 set l [llength $rowisopt]
4997 lset rowisopt $row 0
4998 if {$row + 1 < $l} {
4999 lset rowisopt [expr {$row + 1}] 0
5000 if {$row + 2 < $l} {
5001 lset rowisopt [expr {$row + 2}] 0
5005 set id [lindex $displayorder $row]
5006 if {[info exists iddrawn($id)]} {
5007 set need_redisplay 1
5011 proc insert_pad {row col npad} {
5014 set pad [ntimes $npad {}]
5015 set idlist [lindex $rowidlist $row]
5016 set bef [lrange $idlist 0 [expr {$col - 1}]]
5017 set aft [lrange $idlist $col end]
5018 set i [lsearch -exact $aft {}]
5020 set aft [lreplace $aft $i $i]
5022 lset rowidlist $row [concat $bef $pad $aft]
5026 proc optimize_rows {row col endrow} {
5027 global rowidlist rowisopt displayorder curview children
5032 for {} {$row < $endrow} {incr row; set col 0} {
5033 if {[lindex $rowisopt $row]} continue
5035 set y0 [expr {$row - 1}]
5036 set ym [expr {$row - 2}]
5037 set idlist [lindex $rowidlist $row]
5038 set previdlist [lindex $rowidlist $y0]
5039 if {$idlist eq {} || $previdlist eq {}} continue
5041 set pprevidlist [lindex $rowidlist $ym]
5042 if {$pprevidlist eq {}} continue
5048 for {} {$col < [llength $idlist]} {incr col} {
5049 set id [lindex $idlist $col]
5050 if {[lindex $previdlist $col] eq $id} continue
5055 set x0 [lsearch -exact $previdlist $id]
5056 if {$x0 < 0} continue
5057 set z [expr {$x0 - $col}]
5061 set xm [lsearch -exact $pprevidlist $id]
5063 set z0 [expr {$xm - $x0}]
5067 # if row y0 is the first child of $id then it's not an arrow
5068 if {[lindex $children($curview,$id) 0] ne
5069 [lindex $displayorder $y0]} {
5073 if {!$isarrow && $id ne [lindex $displayorder $row] &&
5074 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
5077 # Looking at lines from this row to the previous row,
5078 # make them go straight up if they end in an arrow on
5079 # the previous row; otherwise make them go straight up
5081 if {$z < -1 || ($z < 0 && $isarrow)} {
5082 # Line currently goes left too much;
5083 # insert pads in the previous row, then optimize it
5084 set npad [expr {-1 - $z + $isarrow}]
5085 insert_pad $y0 $x0 $npad
5087 optimize_rows $y0 $x0 $row
5089 set previdlist [lindex $rowidlist $y0]
5090 set x0 [lsearch -exact $previdlist $id]
5091 set z [expr {$x0 - $col}]
5093 set pprevidlist [lindex $rowidlist $ym]
5094 set xm [lsearch -exact $pprevidlist $id]
5095 set z0 [expr {$xm - $x0}]
5097 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
5098 # Line currently goes right too much;
5099 # insert pads in this line
5100 set npad [expr {$z - 1 + $isarrow}]
5101 insert_pad $row $col $npad
5102 set idlist [lindex $rowidlist $row]
5104 set z [expr {$x0 - $col}]
5107 if {$z0 eq {} && !$isarrow && $ym >= 0} {
5108 # this line links to its first child on row $row-2
5109 set id [lindex $displayorder $ym]
5110 set xc [lsearch -exact $pprevidlist $id]
5112 set z0 [expr {$xc - $x0}]
5115 # avoid lines jigging left then immediately right
5116 if {$z0 ne {} && $z < 0 && $z0 > 0} {
5117 insert_pad $y0 $x0 1
5119 optimize_rows $y0 $x0 $row
5120 set previdlist [lindex $rowidlist $y0]
5124 # Find the first column that doesn't have a line going right
5125 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5126 set id [lindex $idlist $col]
5127 if {$id eq {}} break
5128 set x0 [lsearch -exact $previdlist $id]
5130 # check if this is the link to the first child
5131 set kid [lindex $displayorder $y0]
5132 if {[lindex $children($curview,$id) 0] eq $kid} {
5133 # it is, work out offset to child
5134 set x0 [lsearch -exact $previdlist $kid]
5137 if {$x0 <= $col} break
5139 # Insert a pad at that column as long as it has a line and
5140 # isn't the last column
5141 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5142 set idlist [linsert $idlist $col {}]
5143 lset rowidlist $row $idlist
5151 global canvx0 linespc
5152 return [expr {$canvx0 + $col * $linespc}]
5156 global canvy0 linespc
5157 return [expr {$canvy0 + $row * $linespc}]
5160 proc linewidth {id} {
5161 global thickerline lthickness
5164 if {[info exists thickerline] && $id eq $thickerline} {
5165 set wid [expr {2 * $lthickness}]
5170 proc rowranges {id} {
5171 global curview children uparrowlen downarrowlen
5174 set kids $children($curview,$id)
5180 foreach child $kids {
5181 if {![commitinview $child $curview]} break
5182 set row [rowofcommit $child]
5183 if {![info exists prev]} {
5184 lappend ret [expr {$row + 1}]
5186 if {$row <= $prevrow} {
5187 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5189 # see if the line extends the whole way from prevrow to row
5190 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5191 [lsearch -exact [lindex $rowidlist \
5192 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5193 # it doesn't, see where it ends
5194 set r [expr {$prevrow + $downarrowlen}]
5195 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5196 while {[incr r -1] > $prevrow &&
5197 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5199 while {[incr r] <= $row &&
5200 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5204 # see where it starts up again
5205 set r [expr {$row - $uparrowlen}]
5206 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5207 while {[incr r] < $row &&
5208 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5210 while {[incr r -1] >= $prevrow &&
5211 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5217 if {$child eq $id} {
5226 proc drawlineseg {id row endrow arrowlow} {
5227 global rowidlist displayorder iddrawn linesegs
5228 global canv colormap linespc curview maxlinelen parentlist
5230 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5231 set le [expr {$row + 1}]
5234 set c [lsearch -exact [lindex $rowidlist $le] $id]
5240 set x [lindex $displayorder $le]
5245 if {[info exists iddrawn($x)] || $le == $endrow} {
5246 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5262 if {[info exists linesegs($id)]} {
5263 set lines $linesegs($id)
5265 set r0 [lindex $li 0]
5267 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5277 set li [lindex $lines [expr {$i-1}]]
5278 set r1 [lindex $li 1]
5279 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5284 set x [lindex $cols [expr {$le - $row}]]
5285 set xp [lindex $cols [expr {$le - 1 - $row}]]
5286 set dir [expr {$xp - $x}]
5288 set ith [lindex $lines $i 2]
5289 set coords [$canv coords $ith]
5290 set ah [$canv itemcget $ith -arrow]
5291 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5292 set x2 [lindex $cols [expr {$le + 1 - $row}]]
5293 if {$x2 ne {} && $x - $x2 == $dir} {
5294 set coords [lrange $coords 0 end-2]
5297 set coords [list [xc $le $x] [yc $le]]
5300 set itl [lindex $lines [expr {$i-1}] 2]
5301 set al [$canv itemcget $itl -arrow]
5302 set arrowlow [expr {$al eq "last" || $al eq "both"}]
5303 } elseif {$arrowlow} {
5304 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5305 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5309 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5310 for {set y $le} {[incr y -1] > $row} {} {
5312 set xp [lindex $cols [expr {$y - 1 - $row}]]
5313 set ndir [expr {$xp - $x}]
5314 if {$dir != $ndir || $xp < 0} {
5315 lappend coords [xc $y $x] [yc $y]
5321 # join parent line to first child
5322 set ch [lindex $displayorder $row]
5323 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5325 puts "oops: drawlineseg: child $ch not on row $row"
5326 } elseif {$xc != $x} {
5327 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5328 set d [expr {int(0.5 * $linespc)}]
5331 set x2 [expr {$x1 - $d}]
5333 set x2 [expr {$x1 + $d}]
5336 set y1 [expr {$y2 + $d}]
5337 lappend coords $x1 $y1 $x2 $y2
5338 } elseif {$xc < $x - 1} {
5339 lappend coords [xc $row [expr {$x-1}]] [yc $row]
5340 } elseif {$xc > $x + 1} {
5341 lappend coords [xc $row [expr {$x+1}]] [yc $row]
5345 lappend coords [xc $row $x] [yc $row]
5347 set xn [xc $row $xp]
5349 lappend coords $xn $yn
5353 set t [$canv create line $coords -width [linewidth $id] \
5354 -fill $colormap($id) -tags lines.$id -arrow $arrow]
5357 set lines [linsert $lines $i [list $row $le $t]]
5359 $canv coords $ith $coords
5360 if {$arrow ne $ah} {
5361 $canv itemconf $ith -arrow $arrow
5363 lset lines $i 0 $row
5366 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5367 set ndir [expr {$xo - $xp}]
5368 set clow [$canv coords $itl]
5369 if {$dir == $ndir} {
5370 set clow [lrange $clow 2 end]
5372 set coords [concat $coords $clow]
5374 lset lines [expr {$i-1}] 1 $le
5376 # coalesce two pieces
5378 set b [lindex $lines [expr {$i-1}] 0]
5379 set e [lindex $lines $i 1]
5380 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5382 $canv coords $itl $coords
5383 if {$arrow ne $al} {
5384 $canv itemconf $itl -arrow $arrow
5388 set linesegs($id) $lines
5392 proc drawparentlinks {id row} {
5393 global rowidlist canv colormap curview parentlist
5394 global idpos linespc
5396 set rowids [lindex $rowidlist $row]
5397 set col [lsearch -exact $rowids $id]
5398 if {$col < 0} return
5399 set olds [lindex $parentlist $row]
5400 set row2 [expr {$row + 1}]
5401 set x [xc $row $col]
5404 set d [expr {int(0.5 * $linespc)}]
5405 set ymid [expr {$y + $d}]
5406 set ids [lindex $rowidlist $row2]
5407 # rmx = right-most X coord used
5410 set i [lsearch -exact $ids $p]
5412 puts "oops, parent $p of $id not in list"
5415 set x2 [xc $row2 $i]
5419 set j [lsearch -exact $rowids $p]
5421 # drawlineseg will do this one for us
5425 # should handle duplicated parents here...
5426 set coords [list $x $y]
5428 # if attaching to a vertical segment, draw a smaller
5429 # slant for visual distinctness
5432 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5434 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5436 } elseif {$i < $col && $i < $j} {
5437 # segment slants towards us already
5438 lappend coords [xc $row $j] $y
5440 if {$i < $col - 1} {
5441 lappend coords [expr {$x2 + $linespc}] $y
5442 } elseif {$i > $col + 1} {
5443 lappend coords [expr {$x2 - $linespc}] $y
5445 lappend coords $x2 $y2
5448 lappend coords $x2 $y2
5450 set t [$canv create line $coords -width [linewidth $p] \
5451 -fill $colormap($p) -tags lines.$p]
5455 if {$rmx > [lindex $idpos($id) 1]} {
5456 lset idpos($id) 1 $rmx
5461 proc drawlines {id} {
5464 $canv itemconf lines.$id -width [linewidth $id]
5467 proc drawcmittext {id row col} {
5468 global linespc canv canv2 canv3 fgcolor curview
5469 global cmitlisted commitinfo rowidlist parentlist
5470 global rowtextx idpos idtags idheads idotherrefs
5471 global linehtag linentag linedtag selectedline
5472 global canvxmax boldrows boldnamerows fgcolor
5473 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5475 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5476 set listed $cmitlisted($curview,$id)
5477 if {$id eq $nullid} {
5479 } elseif {$id eq $nullid2} {
5481 } elseif {$id eq $mainheadid} {
5484 set ofill [lindex $circlecolors $listed]
5486 set x [xc $row $col]
5488 set orad [expr {$linespc / 3}]
5490 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5491 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5492 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5493 } elseif {$listed == 3} {
5494 # triangle pointing left for left-side commits
5495 set t [$canv create polygon \
5496 [expr {$x - $orad}] $y \
5497 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5498 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5499 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5501 # triangle pointing right for right-side commits
5502 set t [$canv create polygon \
5503 [expr {$x + $orad - 1}] $y \
5504 [expr {$x - $orad}] [expr {$y - $orad}] \
5505 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5506 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5508 set circleitem($row) $t
5510 $canv bind $t <1> {selcanvline {} %x %y}
5511 set rmx [llength [lindex $rowidlist $row]]
5512 set olds [lindex $parentlist $row]
5514 set nextids [lindex $rowidlist [expr {$row + 1}]]
5516 set i [lsearch -exact $nextids $p]
5522 set xt [xc $row $rmx]
5523 set rowtextx($row) $xt
5524 set idpos($id) [list $x $xt $y]
5525 if {[info exists idtags($id)] || [info exists idheads($id)]
5526 || [info exists idotherrefs($id)]} {
5527 set xt [drawtags $id $x $xt $y]
5529 set headline [lindex $commitinfo($id) 0]
5530 set name [lindex $commitinfo($id) 1]
5531 set date [lindex $commitinfo($id) 2]
5532 set date [formatdate $date]
5535 set isbold [ishighlighted $id]
5537 lappend boldrows $row
5538 set font mainfontbold
5540 lappend boldnamerows $row
5541 set nfont mainfontbold
5544 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
5545 -text $headline -font $font -tags text]
5546 $canv bind $linehtag($row) $ctxbut "rowmenu %X %Y $id"
5547 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5548 -text $name -font $nfont -tags text]
5549 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5550 -text $date -font mainfont -tags text]
5551 if {$selectedline == $row} {
5554 set xr [expr {$xt + [font measure $font $headline]}]
5555 if {$xr > $canvxmax} {
5561 proc drawcmitrow {row} {
5562 global displayorder rowidlist nrows_drawn
5563 global iddrawn markingmatches
5564 global commitinfo numcommits
5565 global filehighlight fhighlights findpattern nhighlights
5566 global hlview vhighlights
5567 global highlight_related rhighlights
5569 if {$row >= $numcommits} return
5571 set id [lindex $displayorder $row]
5572 if {[info exists hlview] && ![info exists vhighlights($id)]} {
5573 askvhighlight $row $id
5575 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5576 askfilehighlight $row $id
5578 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5579 askfindhighlight $row $id
5581 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5582 askrelhighlight $row $id
5584 if {![info exists iddrawn($id)]} {
5585 set col [lsearch -exact [lindex $rowidlist $row] $id]
5587 puts "oops, row $row id $id not in list"
5590 if {![info exists commitinfo($id)]} {
5594 drawcmittext $id $row $col
5598 if {$markingmatches} {
5599 markrowmatches $row $id
5603 proc drawcommits {row {endrow {}}} {
5604 global numcommits iddrawn displayorder curview need_redisplay
5605 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5610 if {$endrow eq {}} {
5613 if {$endrow >= $numcommits} {
5614 set endrow [expr {$numcommits - 1}]
5617 set rl1 [expr {$row - $downarrowlen - 3}]
5621 set ro1 [expr {$row - 3}]
5625 set r2 [expr {$endrow + $uparrowlen + 3}]
5626 if {$r2 > $numcommits} {
5629 for {set r $rl1} {$r < $r2} {incr r} {
5630 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5634 set rl1 [expr {$r + 1}]
5640 optimize_rows $ro1 0 $r2
5641 if {$need_redisplay || $nrows_drawn > 2000} {
5646 # make the lines join to already-drawn rows either side
5647 set r [expr {$row - 1}]
5648 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5651 set er [expr {$endrow + 1}]
5652 if {$er >= $numcommits ||
5653 ![info exists iddrawn([lindex $displayorder $er])]} {
5656 for {} {$r <= $er} {incr r} {
5657 set id [lindex $displayorder $r]
5658 set wasdrawn [info exists iddrawn($id)]
5660 if {$r == $er} break
5661 set nextid [lindex $displayorder [expr {$r + 1}]]
5662 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5663 drawparentlinks $id $r
5665 set rowids [lindex $rowidlist $r]
5666 foreach lid $rowids {
5667 if {$lid eq {}} continue
5668 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5670 # see if this is the first child of any of its parents
5671 foreach p [lindex $parentlist $r] {
5672 if {[lsearch -exact $rowids $p] < 0} {
5673 # make this line extend up to the child
5674 set lineend($p) [drawlineseg $p $r $er 0]
5678 set lineend($lid) [drawlineseg $lid $r $er 1]
5684 proc undolayout {row} {
5685 global uparrowlen mingaplen downarrowlen
5686 global rowidlist rowisopt rowfinal need_redisplay
5688 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5692 if {[llength $rowidlist] > $r} {
5694 set rowidlist [lrange $rowidlist 0 $r]
5695 set rowfinal [lrange $rowfinal 0 $r]
5696 set rowisopt [lrange $rowisopt 0 $r]
5697 set need_redisplay 1
5702 proc drawvisible {} {
5703 global canv linespc curview vrowmod selectedline targetrow targetid
5704 global need_redisplay cscroll numcommits
5706 set fs [$canv yview]
5707 set ymax [lindex [$canv cget -scrollregion] 3]
5708 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5709 set f0 [lindex $fs 0]
5710 set f1 [lindex $fs 1]
5711 set y0 [expr {int($f0 * $ymax)}]
5712 set y1 [expr {int($f1 * $ymax)}]
5714 if {[info exists targetid]} {
5715 if {[commitinview $targetid $curview]} {
5716 set r [rowofcommit $targetid]
5717 if {$r != $targetrow} {
5718 # Fix up the scrollregion and change the scrolling position
5719 # now that our target row has moved.
5720 set diff [expr {($r - $targetrow) * $linespc}]
5723 set ymax [lindex [$canv cget -scrollregion] 3]
5726 set f0 [expr {$y0 / $ymax}]
5727 set f1 [expr {$y1 / $ymax}]
5728 allcanvs yview moveto $f0
5729 $cscroll set $f0 $f1
5730 set need_redisplay 1
5737 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5738 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5739 if {$endrow >= $vrowmod($curview)} {
5740 update_arcrows $curview
5742 if {$selectedline ne {} &&
5743 $row <= $selectedline && $selectedline <= $endrow} {
5744 set targetrow $selectedline
5745 } elseif {[info exists targetid]} {
5746 set targetrow [expr {int(($row + $endrow) / 2)}]
5748 if {[info exists targetrow]} {
5749 if {$targetrow >= $numcommits} {
5750 set targetrow [expr {$numcommits - 1}]
5752 set targetid [commitonrow $targetrow]
5754 drawcommits $row $endrow
5757 proc clear_display {} {
5758 global iddrawn linesegs need_redisplay nrows_drawn
5759 global vhighlights fhighlights nhighlights rhighlights
5760 global linehtag linentag linedtag boldrows boldnamerows
5763 catch {unset iddrawn}
5764 catch {unset linesegs}
5765 catch {unset linehtag}
5766 catch {unset linentag}
5767 catch {unset linedtag}
5770 catch {unset vhighlights}
5771 catch {unset fhighlights}
5772 catch {unset nhighlights}
5773 catch {unset rhighlights}
5774 set need_redisplay 0
5778 proc findcrossings {id} {
5779 global rowidlist parentlist numcommits displayorder
5783 foreach {s e} [rowranges $id] {
5784 if {$e >= $numcommits} {
5785 set e [expr {$numcommits - 1}]
5787 if {$e <= $s} continue
5788 for {set row $e} {[incr row -1] >= $s} {} {
5789 set x [lsearch -exact [lindex $rowidlist $row] $id]
5791 set olds [lindex $parentlist $row]
5792 set kid [lindex $displayorder $row]
5793 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5794 if {$kidx < 0} continue
5795 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5797 set px [lsearch -exact $nextrow $p]
5798 if {$px < 0} continue
5799 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5800 if {[lsearch -exact $ccross $p] >= 0} continue
5801 if {$x == $px + ($kidx < $px? -1: 1)} {
5803 } elseif {[lsearch -exact $cross $p] < 0} {
5810 return [concat $ccross {{}} $cross]
5813 proc assigncolor {id} {
5814 global colormap colors nextcolor
5815 global parents children children curview
5817 if {[info exists colormap($id)]} return
5818 set ncolors [llength $colors]
5819 if {[info exists children($curview,$id)]} {
5820 set kids $children($curview,$id)
5824 if {[llength $kids] == 1} {
5825 set child [lindex $kids 0]
5826 if {[info exists colormap($child)]
5827 && [llength $parents($curview,$child)] == 1} {
5828 set colormap($id) $colormap($child)
5834 foreach x [findcrossings $id] {
5836 # delimiter between corner crossings and other crossings
5837 if {[llength $badcolors] >= $ncolors - 1} break
5838 set origbad $badcolors
5840 if {[info exists colormap($x)]
5841 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5842 lappend badcolors $colormap($x)
5845 if {[llength $badcolors] >= $ncolors} {
5846 set badcolors $origbad
5848 set origbad $badcolors
5849 if {[llength $badcolors] < $ncolors - 1} {
5850 foreach child $kids {
5851 if {[info exists colormap($child)]
5852 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5853 lappend badcolors $colormap($child)
5855 foreach p $parents($curview,$child) {
5856 if {[info exists colormap($p)]
5857 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5858 lappend badcolors $colormap($p)
5862 if {[llength $badcolors] >= $ncolors} {
5863 set badcolors $origbad
5866 for {set i 0} {$i <= $ncolors} {incr i} {
5867 set c [lindex $colors $nextcolor]
5868 if {[incr nextcolor] >= $ncolors} {
5871 if {[lsearch -exact $badcolors $c]} break
5873 set colormap($id) $c
5876 proc bindline {t id} {
5879 $canv bind $t <Enter> "lineenter %x %y $id"
5880 $canv bind $t <Motion> "linemotion %x %y $id"
5881 $canv bind $t <Leave> "lineleave $id"
5882 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5885 proc drawtags {id x xt y1} {
5886 global idtags idheads idotherrefs mainhead
5887 global linespc lthickness
5888 global canv rowtextx curview fgcolor bgcolor ctxbut
5893 if {[info exists idtags($id)]} {
5894 set marks $idtags($id)
5895 set ntags [llength $marks]
5897 if {[info exists idheads($id)]} {
5898 set marks [concat $marks $idheads($id)]
5899 set nheads [llength $idheads($id)]
5901 if {[info exists idotherrefs($id)]} {
5902 set marks [concat $marks $idotherrefs($id)]
5908 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5909 set yt [expr {$y1 - 0.5 * $linespc}]
5910 set yb [expr {$yt + $linespc - 1}]
5914 foreach tag $marks {
5916 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5917 set wid [font measure mainfontbold $tag]
5919 set wid [font measure mainfont $tag]
5923 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5925 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5926 -width $lthickness -fill black -tags tag.$id]
5928 foreach tag $marks x $xvals wid $wvals {
5929 set xl [expr {$x + $delta}]
5930 set xr [expr {$x + $delta + $wid + $lthickness}]
5932 if {[incr ntags -1] >= 0} {
5934 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5935 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5936 -width 1 -outline black -fill yellow -tags tag.$id]
5937 $canv bind $t <1> [list showtag $tag 1]
5938 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5940 # draw a head or other ref
5941 if {[incr nheads -1] >= 0} {
5943 if {$tag eq $mainhead} {
5944 set font mainfontbold
5949 set xl [expr {$xl - $delta/2}]
5950 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5951 -width 1 -outline black -fill $col -tags tag.$id
5952 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5953 set rwid [font measure mainfont $remoteprefix]
5954 set xi [expr {$x + 1}]
5955 set yti [expr {$yt + 1}]
5956 set xri [expr {$x + $rwid}]
5957 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5958 -width 0 -fill "#ffddaa" -tags tag.$id
5961 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5962 -font $font -tags [list tag.$id text]]
5964 $canv bind $t <1> [list showtag $tag 1]
5965 } elseif {$nheads >= 0} {
5966 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
5972 proc xcoord {i level ln} {
5973 global canvx0 xspc1 xspc2
5975 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5976 if {$i > 0 && $i == $level} {
5977 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5978 } elseif {$i > $level} {
5979 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5984 proc show_status {msg} {
5988 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5989 -tags text -fill $fgcolor
5992 # Don't change the text pane cursor if it is currently the hand cursor,
5993 # showing that we are over a sha1 ID link.
5994 proc settextcursor {c} {
5995 global ctext curtextcursor
5997 if {[$ctext cget -cursor] == $curtextcursor} {
5998 $ctext config -cursor $c
6000 set curtextcursor $c
6003 proc nowbusy {what {name {}}} {
6004 global isbusy busyname statusw
6006 if {[array names isbusy] eq {}} {
6007 . config -cursor watch
6011 set busyname($what) $name
6013 $statusw conf -text $name
6017 proc notbusy {what} {
6018 global isbusy maincursor textcursor busyname statusw
6022 if {$busyname($what) ne {} &&
6023 [$statusw cget -text] eq $busyname($what)} {
6024 $statusw conf -text {}
6027 if {[array names isbusy] eq {}} {
6028 . config -cursor $maincursor
6029 settextcursor $textcursor
6033 proc findmatches {f} {
6034 global findtype findstring
6035 if {$findtype == [mc "Regexp"]} {
6036 set matches [regexp -indices -all -inline $findstring $f]
6039 if {$findtype == [mc "IgnCase"]} {
6040 set f [string tolower $f]
6041 set fs [string tolower $fs]
6045 set l [string length $fs]
6046 while {[set j [string first $fs $f $i]] >= 0} {
6047 lappend matches [list $j [expr {$j+$l-1}]]
6048 set i [expr {$j + $l}]
6054 proc dofind {{dirn 1} {wrap 1}} {
6055 global findstring findstartline findcurline selectedline numcommits
6056 global gdttype filehighlight fh_serial find_dirn findallowwrap
6058 if {[info exists find_dirn]} {
6059 if {$find_dirn == $dirn} return
6063 if {$findstring eq {} || $numcommits == 0} return
6064 if {$selectedline eq {}} {
6065 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
6067 set findstartline $selectedline
6069 set findcurline $findstartline
6070 nowbusy finding [mc "Searching"]
6071 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
6072 after cancel do_file_hl $fh_serial
6073 do_file_hl $fh_serial
6076 set findallowwrap $wrap
6080 proc stopfinding {} {
6081 global find_dirn findcurline fprogcoord
6083 if {[info exists find_dirn]} {
6094 global commitdata commitinfo numcommits findpattern findloc
6095 global findstartline findcurline findallowwrap
6096 global find_dirn gdttype fhighlights fprogcoord
6097 global curview varcorder vrownum varccommits vrowmod
6099 if {![info exists find_dirn]} {
6102 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
6105 if {$find_dirn > 0} {
6107 if {$l >= $numcommits} {
6110 if {$l <= $findstartline} {
6111 set lim [expr {$findstartline + 1}]
6114 set moretodo $findallowwrap
6121 if {$l >= $findstartline} {
6122 set lim [expr {$findstartline - 1}]
6125 set moretodo $findallowwrap
6128 set n [expr {($lim - $l) * $find_dirn}]
6133 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6134 update_arcrows $curview
6138 set ai [bsearch $vrownum($curview) $l]
6139 set a [lindex $varcorder($curview) $ai]
6140 set arow [lindex $vrownum($curview) $ai]
6141 set ids [lindex $varccommits($curview,$a)]
6142 set arowend [expr {$arow + [llength $ids]}]
6143 if {$gdttype eq [mc "containing:"]} {
6144 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6145 if {$l < $arow || $l >= $arowend} {
6147 set a [lindex $varcorder($curview) $ai]
6148 set arow [lindex $vrownum($curview) $ai]
6149 set ids [lindex $varccommits($curview,$a)]
6150 set arowend [expr {$arow + [llength $ids]}]
6152 set id [lindex $ids [expr {$l - $arow}]]
6153 # shouldn't happen unless git log doesn't give all the commits...
6154 if {![info exists commitdata($id)] ||
6155 ![doesmatch $commitdata($id)]} {
6158 if {![info exists commitinfo($id)]} {
6161 set info $commitinfo($id)
6162 foreach f $info ty $fldtypes {
6163 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6172 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6173 if {$l < $arow || $l >= $arowend} {
6175 set a [lindex $varcorder($curview) $ai]
6176 set arow [lindex $vrownum($curview) $ai]
6177 set ids [lindex $varccommits($curview,$a)]
6178 set arowend [expr {$arow + [llength $ids]}]
6180 set id [lindex $ids [expr {$l - $arow}]]
6181 if {![info exists fhighlights($id)]} {
6182 # this sets fhighlights($id) to -1
6183 askfilehighlight $l $id
6185 if {$fhighlights($id) > 0} {
6189 if {$fhighlights($id) < 0} {
6192 set findcurline [expr {$l - $find_dirn}]
6197 if {$found || ($domore && !$moretodo)} {
6213 set findcurline [expr {$l - $find_dirn}]
6215 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6219 set fprogcoord [expr {$n * 1.0 / $numcommits}]
6224 proc findselectline {l} {
6225 global findloc commentend ctext findcurline markingmatches gdttype
6227 set markingmatches 1
6230 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
6231 # highlight the matches in the comments
6232 set f [$ctext get 1.0 $commentend]
6233 set matches [findmatches $f]
6234 foreach match $matches {
6235 set start [lindex $match 0]
6236 set end [expr {[lindex $match 1] + 1}]
6237 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6243 # mark the bits of a headline or author that match a find string
6244 proc markmatches {canv l str tag matches font row} {
6247 set bbox [$canv bbox $tag]
6248 set x0 [lindex $bbox 0]
6249 set y0 [lindex $bbox 1]
6250 set y1 [lindex $bbox 3]
6251 foreach match $matches {
6252 set start [lindex $match 0]
6253 set end [lindex $match 1]
6254 if {$start > $end} continue
6255 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6256 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6257 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6258 [expr {$x0+$xlen+2}] $y1 \
6259 -outline {} -tags [list match$l matches] -fill yellow]
6261 if {$row == $selectedline} {
6262 $canv raise $t secsel
6267 proc unmarkmatches {} {
6268 global markingmatches
6270 allcanvs delete matches
6271 set markingmatches 0
6275 proc selcanvline {w x y} {
6276 global canv canvy0 ctext linespc
6278 set ymax [lindex [$canv cget -scrollregion] 3]
6279 if {$ymax == {}} return
6280 set yfrac [lindex [$canv yview] 0]
6281 set y [expr {$y + $yfrac * $ymax}]
6282 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6287 set xmax [lindex [$canv cget -scrollregion] 2]
6288 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6289 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6295 proc commit_descriptor {p} {
6297 if {![info exists commitinfo($p)]} {
6301 if {[llength $commitinfo($p)] > 1} {
6302 set l [lindex $commitinfo($p) 0]
6307 # append some text to the ctext widget, and make any SHA1 ID
6308 # that we know about be a clickable link.
6309 proc appendwithlinks {text tags} {
6310 global ctext linknum curview
6312 set start [$ctext index "end - 1c"]
6313 $ctext insert end $text $tags
6314 set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6318 set linkid [string range $text $s $e]
6320 $ctext tag delete link$linknum
6321 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6322 setlink $linkid link$linknum
6327 proc setlink {id lk} {
6328 global curview ctext pendinglinks
6331 if {[string length $id] < 40} {
6332 set matches [longid $id]
6333 if {[llength $matches] > 0} {
6334 if {[llength $matches] > 1} return
6336 set id [lindex $matches 0]
6339 set known [commitinview $id $curview]
6342 $ctext tag conf $lk -foreground blue -underline 1
6343 $ctext tag bind $lk <1> [list selbyid $id]
6344 $ctext tag bind $lk <Enter> {linkcursor %W 1}
6345 $ctext tag bind $lk <Leave> {linkcursor %W -1}
6347 lappend pendinglinks($id) $lk
6348 interestedin $id {makelink %P}
6352 proc makelink {id} {
6355 if {![info exists pendinglinks($id)]} return
6356 foreach lk $pendinglinks($id) {
6359 unset pendinglinks($id)
6362 proc linkcursor {w inc} {
6363 global linkentercount curtextcursor
6365 if {[incr linkentercount $inc] > 0} {
6366 $w configure -cursor hand2
6368 $w configure -cursor $curtextcursor
6369 if {$linkentercount < 0} {
6370 set linkentercount 0
6375 proc viewnextline {dir} {
6379 set ymax [lindex [$canv cget -scrollregion] 3]
6380 set wnow [$canv yview]
6381 set wtop [expr {[lindex $wnow 0] * $ymax}]
6382 set newtop [expr {$wtop + $dir * $linespc}]
6385 } elseif {$newtop > $ymax} {
6388 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6391 # add a list of tag or branch names at position pos
6392 # returns the number of names inserted
6393 proc appendrefs {pos ids var} {
6394 global ctext linknum curview $var maxrefs
6396 if {[catch {$ctext index $pos}]} {
6399 $ctext conf -state normal
6400 $ctext delete $pos "$pos lineend"
6403 foreach tag [set $var\($id\)] {
6404 lappend tags [list $tag $id]
6407 if {[llength $tags] > $maxrefs} {
6408 $ctext insert $pos "many ([llength $tags])"
6410 set tags [lsort -index 0 -decreasing $tags]
6413 set id [lindex $ti 1]
6416 $ctext tag delete $lk
6417 $ctext insert $pos $sep
6418 $ctext insert $pos [lindex $ti 0] $lk
6423 $ctext conf -state disabled
6424 return [llength $tags]
6427 # called when we have finished computing the nearby tags
6428 proc dispneartags {delay} {
6429 global selectedline currentid showneartags tagphase
6431 if {$selectedline eq {} || !$showneartags} return
6432 after cancel dispnexttag
6434 after 200 dispnexttag
6437 after idle dispnexttag
6442 proc dispnexttag {} {
6443 global selectedline currentid showneartags tagphase ctext
6445 if {$selectedline eq {} || !$showneartags} return
6446 switch -- $tagphase {
6448 set dtags [desctags $currentid]
6450 appendrefs precedes $dtags idtags
6454 set atags [anctags $currentid]
6456 appendrefs follows $atags idtags
6460 set dheads [descheads $currentid]
6461 if {$dheads ne {}} {
6462 if {[appendrefs branch $dheads idheads] > 1
6463 && [$ctext get "branch -3c"] eq "h"} {
6464 # turn "Branch" into "Branches"
6465 $ctext conf -state normal
6466 $ctext insert "branch -2c" "es"
6467 $ctext conf -state disabled
6472 if {[incr tagphase] <= 2} {
6473 after idle dispnexttag
6477 proc make_secsel {l} {
6478 global linehtag linentag linedtag canv canv2 canv3
6480 if {![info exists linehtag($l)]} return
6482 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
6483 -tags secsel -fill [$canv cget -selectbackground]]
6485 $canv2 delete secsel
6486 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
6487 -tags secsel -fill [$canv2 cget -selectbackground]]
6489 $canv3 delete secsel
6490 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
6491 -tags secsel -fill [$canv3 cget -selectbackground]]
6495 proc selectline {l isnew {desired_loc {}}} {
6496 global canv ctext commitinfo selectedline
6497 global canvy0 linespc parents children curview
6498 global currentid sha1entry
6499 global commentend idtags linknum
6500 global mergemax numcommits pending_select
6501 global cmitmode showneartags allcommits
6502 global targetrow targetid lastscrollrows
6503 global autoselect jump_to_here
6505 catch {unset pending_select}
6510 if {$l < 0 || $l >= $numcommits} return
6511 set id [commitonrow $l]
6516 if {$lastscrollrows < $numcommits} {
6520 set y [expr {$canvy0 + $l * $linespc}]
6521 set ymax [lindex [$canv cget -scrollregion] 3]
6522 set ytop [expr {$y - $linespc - 1}]
6523 set ybot [expr {$y + $linespc + 1}]
6524 set wnow [$canv yview]
6525 set wtop [expr {[lindex $wnow 0] * $ymax}]
6526 set wbot [expr {[lindex $wnow 1] * $ymax}]
6527 set wh [expr {$wbot - $wtop}]
6529 if {$ytop < $wtop} {
6530 if {$ybot < $wtop} {
6531 set newtop [expr {$y - $wh / 2.0}]
6534 if {$newtop > $wtop - $linespc} {
6535 set newtop [expr {$wtop - $linespc}]
6538 } elseif {$ybot > $wbot} {
6539 if {$ytop > $wbot} {
6540 set newtop [expr {$y - $wh / 2.0}]
6542 set newtop [expr {$ybot - $wh}]
6543 if {$newtop < $wtop + $linespc} {
6544 set newtop [expr {$wtop + $linespc}]
6548 if {$newtop != $wtop} {
6552 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6559 addtohistory [list selbyid $id]
6562 $sha1entry delete 0 end
6563 $sha1entry insert 0 $id
6565 $sha1entry selection from 0
6566 $sha1entry selection to end
6570 $ctext conf -state normal
6573 if {![info exists commitinfo($id)]} {
6576 set info $commitinfo($id)
6577 set date [formatdate [lindex $info 2]]
6578 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
6579 set date [formatdate [lindex $info 4]]
6580 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
6581 if {[info exists idtags($id)]} {
6582 $ctext insert end [mc "Tags:"]
6583 foreach tag $idtags($id) {
6584 $ctext insert end " $tag"
6586 $ctext insert end "\n"
6590 set olds $parents($curview,$id)
6591 if {[llength $olds] > 1} {
6594 if {$np >= $mergemax} {
6599 $ctext insert end "[mc "Parent"]: " $tag
6600 appendwithlinks [commit_descriptor $p] {}
6605 append headers "[mc "Parent"]: [commit_descriptor $p]"
6609 foreach c $children($curview,$id) {
6610 append headers "[mc "Child"]: [commit_descriptor $c]"
6613 # make anything that looks like a SHA1 ID be a clickable link
6614 appendwithlinks $headers {}
6615 if {$showneartags} {
6616 if {![info exists allcommits]} {
6619 $ctext insert end "[mc "Branch"]: "
6620 $ctext mark set branch "end -1c"
6621 $ctext mark gravity branch left
6622 $ctext insert end "\n[mc "Follows"]: "
6623 $ctext mark set follows "end -1c"
6624 $ctext mark gravity follows left
6625 $ctext insert end "\n[mc "Precedes"]: "
6626 $ctext mark set precedes "end -1c"
6627 $ctext mark gravity precedes left
6628 $ctext insert end "\n"
6631 $ctext insert end "\n"
6632 set comment [lindex $info 5]
6633 if {[string first "\r" $comment] >= 0} {
6634 set comment [string map {"\r" "\n "} $comment]
6636 appendwithlinks $comment {comment}
6638 $ctext tag remove found 1.0 end
6639 $ctext conf -state disabled
6640 set commentend [$ctext index "end - 1c"]
6642 set jump_to_here $desired_loc
6643 init_flist [mc "Comments"]
6644 if {$cmitmode eq "tree"} {
6646 } elseif {[llength $olds] <= 1} {
6653 proc selfirstline {} {
6658 proc sellastline {} {
6661 set l [expr {$numcommits - 1}]
6665 proc selnextline {dir} {
6668 if {$selectedline eq {}} return
6669 set l [expr {$selectedline + $dir}]
6674 proc selnextpage {dir} {
6675 global canv linespc selectedline numcommits
6677 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6681 allcanvs yview scroll [expr {$dir * $lpp}] units
6683 if {$selectedline eq {}} return
6684 set l [expr {$selectedline + $dir * $lpp}]
6687 } elseif {$l >= $numcommits} {
6688 set l [expr $numcommits - 1]
6694 proc unselectline {} {
6695 global selectedline currentid
6698 catch {unset currentid}
6699 allcanvs delete secsel
6703 proc reselectline {} {
6706 if {$selectedline ne {}} {
6707 selectline $selectedline 0
6711 proc addtohistory {cmd} {
6712 global history historyindex curview
6714 set elt [list $curview $cmd]
6715 if {$historyindex > 0
6716 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6720 if {$historyindex < [llength $history]} {
6721 set history [lreplace $history $historyindex end $elt]
6723 lappend history $elt
6726 if {$historyindex > 1} {
6727 .tf.bar.leftbut conf -state normal
6729 .tf.bar.leftbut conf -state disabled
6731 .tf.bar.rightbut conf -state disabled
6737 set view [lindex $elt 0]
6738 set cmd [lindex $elt 1]
6739 if {$curview != $view} {
6746 global history historyindex
6749 if {$historyindex > 1} {
6750 incr historyindex -1
6751 godo [lindex $history [expr {$historyindex - 1}]]
6752 .tf.bar.rightbut conf -state normal
6754 if {$historyindex <= 1} {
6755 .tf.bar.leftbut conf -state disabled
6760 global history historyindex
6763 if {$historyindex < [llength $history]} {
6764 set cmd [lindex $history $historyindex]
6767 .tf.bar.leftbut conf -state normal
6769 if {$historyindex >= [llength $history]} {
6770 .tf.bar.rightbut conf -state disabled
6775 global treefilelist treeidlist diffids diffmergeid treepending
6776 global nullid nullid2
6779 catch {unset diffmergeid}
6780 if {![info exists treefilelist($id)]} {
6781 if {![info exists treepending]} {
6782 if {$id eq $nullid} {
6783 set cmd [list | git ls-files]
6784 } elseif {$id eq $nullid2} {
6785 set cmd [list | git ls-files --stage -t]
6787 set cmd [list | git ls-tree -r $id]
6789 if {[catch {set gtf [open $cmd r]}]} {
6793 set treefilelist($id) {}
6794 set treeidlist($id) {}
6795 fconfigure $gtf -blocking 0 -encoding binary
6796 filerun $gtf [list gettreeline $gtf $id]
6803 proc gettreeline {gtf id} {
6804 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6807 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6808 if {$diffids eq $nullid} {
6811 set i [string first "\t" $line]
6812 if {$i < 0} continue
6813 set fname [string range $line [expr {$i+1}] end]
6814 set line [string range $line 0 [expr {$i-1}]]
6815 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6816 set sha1 [lindex $line 2]
6817 lappend treeidlist($id) $sha1
6819 if {[string index $fname 0] eq "\""} {
6820 set fname [lindex $fname 0]
6822 set fname [encoding convertfrom $fname]
6823 lappend treefilelist($id) $fname
6826 return [expr {$nl >= 1000? 2: 1}]
6830 if {$cmitmode ne "tree"} {
6831 if {![info exists diffmergeid]} {
6832 gettreediffs $diffids
6834 } elseif {$id ne $diffids} {
6843 global treefilelist treeidlist diffids nullid nullid2
6844 global ctext_file_names ctext_file_lines
6845 global ctext commentend
6847 set i [lsearch -exact $treefilelist($diffids) $f]
6849 puts "oops, $f not in list for id $diffids"
6852 if {$diffids eq $nullid} {
6853 if {[catch {set bf [open $f r]} err]} {
6854 puts "oops, can't read $f: $err"
6858 set blob [lindex $treeidlist($diffids) $i]
6859 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6860 puts "oops, error reading blob $blob: $err"
6864 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
6865 filerun $bf [list getblobline $bf $diffids]
6866 $ctext config -state normal
6867 clear_ctext $commentend
6868 lappend ctext_file_names $f
6869 lappend ctext_file_lines [lindex [split $commentend "."] 0]
6870 $ctext insert end "\n"
6871 $ctext insert end "$f\n" filesep
6872 $ctext config -state disabled
6873 $ctext yview $commentend
6877 proc getblobline {bf id} {
6878 global diffids cmitmode ctext
6880 if {$id ne $diffids || $cmitmode ne "tree"} {
6884 $ctext config -state normal
6886 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6887 $ctext insert end "$line\n"
6890 global jump_to_here ctext_file_names commentend
6892 # delete last newline
6893 $ctext delete "end - 2c" "end - 1c"
6895 if {$jump_to_here ne {} &&
6896 [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
6897 set lnum [expr {[lindex $jump_to_here 1] +
6898 [lindex [split $commentend .] 0]}]
6899 mark_ctext_line $lnum
6903 $ctext config -state disabled
6904 return [expr {$nl >= 1000? 2: 1}]
6907 proc mark_ctext_line {lnum} {
6908 global ctext markbgcolor
6910 $ctext tag delete omark
6911 $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
6912 $ctext tag conf omark -background $markbgcolor
6916 proc mergediff {id} {
6918 global diffids treediffs
6919 global parents curview
6923 set treediffs($id) {}
6924 set np [llength $parents($curview,$id)]
6929 proc startdiff {ids} {
6930 global treediffs diffids treepending diffmergeid nullid nullid2
6934 catch {unset diffmergeid}
6935 if {![info exists treediffs($ids)] ||
6936 [lsearch -exact $ids $nullid] >= 0 ||
6937 [lsearch -exact $ids $nullid2] >= 0} {
6938 if {![info exists treepending]} {
6946 proc path_filter {filter name} {
6948 set l [string length $p]
6949 if {[string index $p end] eq "/"} {
6950 if {[string compare -length $l $p $name] == 0} {
6954 if {[string compare -length $l $p $name] == 0 &&
6955 ([string length $name] == $l ||
6956 [string index $name $l] eq "/")} {
6964 proc addtocflist {ids} {
6967 add_flist $treediffs($ids)
6971 proc diffcmd {ids flags} {
6972 global nullid nullid2
6974 set i [lsearch -exact $ids $nullid]
6975 set j [lsearch -exact $ids $nullid2]
6977 if {[llength $ids] > 1 && $j < 0} {
6978 # comparing working directory with some specific revision
6979 set cmd [concat | git diff-index $flags]
6981 lappend cmd -R [lindex $ids 1]
6983 lappend cmd [lindex $ids 0]
6986 # comparing working directory with index
6987 set cmd [concat | git diff-files $flags]
6992 } elseif {$j >= 0} {
6993 set cmd [concat | git diff-index --cached $flags]
6994 if {[llength $ids] > 1} {
6995 # comparing index with specific revision
6997 lappend cmd -R [lindex $ids 1]
6999 lappend cmd [lindex $ids 0]
7002 # comparing index with HEAD
7006 set cmd [concat | git diff-tree -r $flags $ids]
7011 proc gettreediffs {ids} {
7012 global treediff treepending
7014 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
7016 set treepending $ids
7018 fconfigure $gdtf -blocking 0 -encoding binary
7019 filerun $gdtf [list gettreediffline $gdtf $ids]
7022 proc gettreediffline {gdtf ids} {
7023 global treediff treediffs treepending diffids diffmergeid
7024 global cmitmode vfilelimit curview limitdiffs perfile_attrs
7029 if {$perfile_attrs} {
7030 # cache_gitattr is slow, and even slower on win32 where we
7031 # have to invoke it for only about 30 paths at a time
7033 if {[tk windowingsystem] == "win32"} {
7037 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
7038 set i [string first "\t" $line]
7040 set file [string range $line [expr {$i+1}] end]
7041 if {[string index $file 0] eq "\""} {
7042 set file [lindex $file 0]
7044 set file [encoding convertfrom $file]
7045 if {$file ne [lindex $treediff end]} {
7046 lappend treediff $file
7047 lappend sublist $file
7051 if {$perfile_attrs} {
7052 cache_gitattr encoding $sublist
7055 return [expr {$nr >= $max? 2: 1}]
7058 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7060 foreach f $treediff {
7061 if {[path_filter $vfilelimit($curview) $f]} {
7065 set treediffs($ids) $flist
7067 set treediffs($ids) $treediff
7070 if {$cmitmode eq "tree"} {
7072 } elseif {$ids != $diffids} {
7073 if {![info exists diffmergeid]} {
7074 gettreediffs $diffids
7082 # empty string or positive integer
7083 proc diffcontextvalidate {v} {
7084 return [regexp {^(|[1-9][0-9]*)$} $v]
7087 proc diffcontextchange {n1 n2 op} {
7088 global diffcontextstring diffcontext
7090 if {[string is integer -strict $diffcontextstring]} {
7091 if {$diffcontextstring > 0} {
7092 set diffcontext $diffcontextstring
7098 proc changeignorespace {} {
7102 proc getblobdiffs {ids} {
7103 global blobdifffd diffids env
7104 global diffinhdr treediffs
7107 global limitdiffs vfilelimit curview
7108 global diffencoding targetline diffnparents
7110 set cmd [diffcmd $ids "-p -C --cc --no-commit-id -U$diffcontext"]
7114 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7115 set cmd [concat $cmd -- $vfilelimit($curview)]
7117 if {[catch {set bdf [open $cmd r]} err]} {
7118 error_popup [mc "Error getting diffs: %s" $err]
7124 set diffencoding [get_path_encoding {}]
7125 fconfigure $bdf -blocking 0 -encoding binary
7126 set blobdifffd($ids) $bdf
7127 filerun $bdf [list getblobdiffline $bdf $diffids]
7130 proc setinlist {var i val} {
7133 while {[llength [set $var]] < $i} {
7136 if {[llength [set $var]] == $i} {
7143 proc makediffhdr {fname ids} {
7144 global ctext curdiffstart treediffs diffencoding
7145 global ctext_file_names jump_to_here targetline diffline
7147 set fname [encoding convertfrom $fname]
7148 set diffencoding [get_path_encoding $fname]
7149 set i [lsearch -exact $treediffs($ids) $fname]
7151 setinlist difffilestart $i $curdiffstart
7153 lset ctext_file_names end $fname
7154 set l [expr {(78 - [string length $fname]) / 2}]
7155 set pad [string range "----------------------------------------" 1 $l]
7156 $ctext insert $curdiffstart "$pad $fname $pad" filesep
7158 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7159 set targetline [lindex $jump_to_here 1]
7164 proc getblobdiffline {bdf ids} {
7165 global diffids blobdifffd ctext curdiffstart
7166 global diffnexthead diffnextnote difffilestart
7167 global ctext_file_names ctext_file_lines
7168 global diffinhdr treediffs mergemax diffnparents
7169 global diffencoding jump_to_here targetline diffline
7172 $ctext conf -state normal
7173 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7174 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7178 if {![string compare -length 5 "diff " $line]} {
7179 if {![regexp {^diff (--cc|--git) } $line m type]} {
7180 set line [encoding convertfrom $line]
7181 $ctext insert end "$line\n" hunksep
7184 # start of a new file
7186 $ctext insert end "\n"
7187 set curdiffstart [$ctext index "end - 1c"]
7188 lappend ctext_file_names ""
7189 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7190 $ctext insert end "\n" filesep
7192 if {$type eq "--cc"} {
7193 # start of a new file in a merge diff
7194 set fname [string range $line 10 end]
7195 if {[lsearch -exact $treediffs($ids) $fname] < 0} {
7196 lappend treediffs($ids) $fname
7197 add_flist [list $fname]
7201 set line [string range $line 11 end]
7202 # If the name hasn't changed the length will be odd,
7203 # the middle char will be a space, and the two bits either
7204 # side will be a/name and b/name, or "a/name" and "b/name".
7205 # If the name has changed we'll get "rename from" and
7206 # "rename to" or "copy from" and "copy to" lines following
7207 # this, and we'll use them to get the filenames.
7208 # This complexity is necessary because spaces in the
7209 # filename(s) don't get escaped.
7210 set l [string length $line]
7211 set i [expr {$l / 2}]
7212 if {!(($l & 1) && [string index $line $i] eq " " &&
7213 [string range $line 2 [expr {$i - 1}]] eq \
7214 [string range $line [expr {$i + 3}] end])} {
7217 # unescape if quoted and chop off the a/ from the front
7218 if {[string index $line 0] eq "\""} {
7219 set fname [string range [lindex $line 0] 2 end]
7221 set fname [string range $line 2 [expr {$i - 1}]]
7224 makediffhdr $fname $ids
7226 } elseif {![string compare -length 16 "* Unmerged path " $line]} {
7227 set fname [encoding convertfrom [string range $line 16 end]]
7228 $ctext insert end "\n"
7229 set curdiffstart [$ctext index "end - 1c"]
7230 lappend ctext_file_names $fname
7231 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7232 $ctext insert end "$line\n" filesep
7233 set i [lsearch -exact $treediffs($ids) $fname]
7235 setinlist difffilestart $i $curdiffstart
7238 } elseif {![string compare -length 2 "@@" $line]} {
7239 regexp {^@@+} $line ats
7240 set line [encoding convertfrom $diffencoding $line]
7241 $ctext insert end "$line\n" hunksep
7242 if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
7245 set diffnparents [expr {[string length $ats] - 1}]
7248 } elseif {$diffinhdr} {
7249 if {![string compare -length 12 "rename from " $line]} {
7250 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7251 if {[string index $fname 0] eq "\""} {
7252 set fname [lindex $fname 0]
7254 set fname [encoding convertfrom $fname]
7255 set i [lsearch -exact $treediffs($ids) $fname]
7257 setinlist difffilestart $i $curdiffstart
7259 } elseif {![string compare -length 10 $line "rename to "] ||
7260 ![string compare -length 8 $line "copy to "]} {
7261 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7262 if {[string index $fname 0] eq "\""} {
7263 set fname [lindex $fname 0]
7265 makediffhdr $fname $ids
7266 } elseif {[string compare -length 3 $line "---"] == 0} {
7269 } elseif {[string compare -length 3 $line "+++"] == 0} {
7273 $ctext insert end "$line\n" filesep
7276 set line [encoding convertfrom $diffencoding $line]
7277 # parse the prefix - one ' ', '-' or '+' for each parent
7278 set prefix [string range $line 0 [expr {$diffnparents - 1}]]
7279 set tag [expr {$diffnparents > 1? "m": "d"}]
7280 if {[string trim $prefix " -+"] eq {}} {
7281 # prefix only has " ", "-" and "+" in it: normal diff line
7282 set num [string first "-" $prefix]
7284 # removed line, first parent with line is $num
7285 if {$num >= $mergemax} {
7288 $ctext insert end "$line\n" $tag$num
7291 if {[string first "+" $prefix] >= 0} {
7293 lappend tags ${tag}result
7294 if {$diffnparents > 1} {
7295 set num [string first " " $prefix]
7297 if {$num >= $mergemax} {
7304 if {$targetline ne {}} {
7305 if {$diffline == $targetline} {
7306 set seehere [$ctext index "end - 1 chars"]
7312 $ctext insert end "$line\n" $tags
7315 # "\ No newline at end of file",
7316 # or something else we don't recognize
7317 $ctext insert end "$line\n" hunksep
7321 if {[info exists seehere]} {
7322 mark_ctext_line [lindex [split $seehere .] 0]
7324 $ctext conf -state disabled
7329 return [expr {$nr >= 1000? 2: 1}]
7332 proc changediffdisp {} {
7333 global ctext diffelide
7335 $ctext tag conf d0 -elide [lindex $diffelide 0]
7336 $ctext tag conf dresult -elide [lindex $diffelide 1]
7339 proc highlightfile {loc cline} {
7340 global ctext cflist cflist_top
7343 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7344 $cflist tag add highlight $cline.0 "$cline.0 lineend"
7345 $cflist see $cline.0
7346 set cflist_top $cline
7350 global difffilestart ctext cmitmode
7352 if {$cmitmode eq "tree"} return
7355 set here [$ctext index @0,0]
7356 foreach loc $difffilestart {
7357 if {[$ctext compare $loc >= $here]} {
7358 highlightfile $prev $prevline
7364 highlightfile $prev $prevline
7368 global difffilestart ctext cmitmode
7370 if {$cmitmode eq "tree"} return
7371 set here [$ctext index @0,0]
7373 foreach loc $difffilestart {
7375 if {[$ctext compare $loc > $here]} {
7376 highlightfile $loc $line
7382 proc clear_ctext {{first 1.0}} {
7383 global ctext smarktop smarkbot
7384 global ctext_file_names ctext_file_lines
7387 set l [lindex [split $first .] 0]
7388 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
7391 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
7394 $ctext delete $first end
7395 if {$first eq "1.0"} {
7396 catch {unset pendinglinks}
7398 set ctext_file_names {}
7399 set ctext_file_lines {}
7402 proc settabs {{firstab {}}} {
7403 global firsttabstop tabstop ctext have_tk85
7405 if {$firstab ne {} && $have_tk85} {
7406 set firsttabstop $firstab
7408 set w [font measure textfont "0"]
7409 if {$firsttabstop != 0} {
7410 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
7411 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
7412 } elseif {$have_tk85 || $tabstop != 8} {
7413 $ctext conf -tabs [expr {$tabstop * $w}]
7415 $ctext conf -tabs {}
7419 proc incrsearch {name ix op} {
7420 global ctext searchstring searchdirn
7422 $ctext tag remove found 1.0 end
7423 if {[catch {$ctext index anchor}]} {
7424 # no anchor set, use start of selection, or of visible area
7425 set sel [$ctext tag ranges sel]
7427 $ctext mark set anchor [lindex $sel 0]
7428 } elseif {$searchdirn eq "-forwards"} {
7429 $ctext mark set anchor @0,0
7431 $ctext mark set anchor @0,[winfo height $ctext]
7434 if {$searchstring ne {}} {
7435 set here [$ctext search $searchdirn -- $searchstring anchor]
7444 global sstring ctext searchstring searchdirn
7447 $sstring icursor end
7448 set searchdirn -forwards
7449 if {$searchstring ne {}} {
7450 set sel [$ctext tag ranges sel]
7452 set start "[lindex $sel 0] + 1c"
7453 } elseif {[catch {set start [$ctext index anchor]}]} {
7456 set match [$ctext search -count mlen -- $searchstring $start]
7457 $ctext tag remove sel 1.0 end
7463 set mend "$match + $mlen c"
7464 $ctext tag add sel $match $mend
7465 $ctext mark unset anchor
7469 proc dosearchback {} {
7470 global sstring ctext searchstring searchdirn
7473 $sstring icursor end
7474 set searchdirn -backwards
7475 if {$searchstring ne {}} {
7476 set sel [$ctext tag ranges sel]
7478 set start [lindex $sel 0]
7479 } elseif {[catch {set start [$ctext index anchor]}]} {
7480 set start @0,[winfo height $ctext]
7482 set match [$ctext search -backwards -count ml -- $searchstring $start]
7483 $ctext tag remove sel 1.0 end
7489 set mend "$match + $ml c"
7490 $ctext tag add sel $match $mend
7491 $ctext mark unset anchor
7495 proc searchmark {first last} {
7496 global ctext searchstring
7500 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
7501 if {$match eq {}} break
7502 set mend "$match + $mlen c"
7503 $ctext tag add found $match $mend
7507 proc searchmarkvisible {doall} {
7508 global ctext smarktop smarkbot
7510 set topline [lindex [split [$ctext index @0,0] .] 0]
7511 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
7512 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
7513 # no overlap with previous
7514 searchmark $topline $botline
7515 set smarktop $topline
7516 set smarkbot $botline
7518 if {$topline < $smarktop} {
7519 searchmark $topline [expr {$smarktop-1}]
7520 set smarktop $topline
7522 if {$botline > $smarkbot} {
7523 searchmark [expr {$smarkbot+1}] $botline
7524 set smarkbot $botline
7529 proc scrolltext {f0 f1} {
7532 .bleft.bottom.sb set $f0 $f1
7533 if {$searchstring ne {}} {
7539 global linespc charspc canvx0 canvy0
7540 global xspc1 xspc2 lthickness
7542 set linespc [font metrics mainfont -linespace]
7543 set charspc [font measure mainfont "m"]
7544 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
7545 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
7546 set lthickness [expr {int($linespc / 9) + 1}]
7547 set xspc1(0) $linespc
7555 set ymax [lindex [$canv cget -scrollregion] 3]
7556 if {$ymax eq {} || $ymax == 0} return
7557 set span [$canv yview]
7560 allcanvs yview moveto [lindex $span 0]
7562 if {$selectedline ne {}} {
7563 selectline $selectedline 0
7564 allcanvs yview moveto [lindex $span 0]
7568 proc parsefont {f n} {
7571 set fontattr($f,family) [lindex $n 0]
7573 if {$s eq {} || $s == 0} {
7576 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
7578 set fontattr($f,size) $s
7579 set fontattr($f,weight) normal
7580 set fontattr($f,slant) roman
7581 foreach style [lrange $n 2 end] {
7584 "bold" {set fontattr($f,weight) $style}
7586 "italic" {set fontattr($f,slant) $style}
7591 proc fontflags {f {isbold 0}} {
7594 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
7595 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
7596 -slant $fontattr($f,slant)]
7602 set n [list $fontattr($f,family) $fontattr($f,size)]
7603 if {$fontattr($f,weight) eq "bold"} {
7606 if {$fontattr($f,slant) eq "italic"} {
7612 proc incrfont {inc} {
7613 global mainfont textfont ctext canv cflist showrefstop
7614 global stopped entries fontattr
7617 set s $fontattr(mainfont,size)
7622 set fontattr(mainfont,size) $s
7623 font config mainfont -size $s
7624 font config mainfontbold -size $s
7625 set mainfont [fontname mainfont]
7626 set s $fontattr(textfont,size)
7631 set fontattr(textfont,size) $s
7632 font config textfont -size $s
7633 font config textfontbold -size $s
7634 set textfont [fontname textfont]
7641 global sha1entry sha1string
7642 if {[string length $sha1string] == 40} {
7643 $sha1entry delete 0 end
7647 proc sha1change {n1 n2 op} {
7648 global sha1string currentid sha1but
7649 if {$sha1string == {}
7650 || ([info exists currentid] && $sha1string == $currentid)} {
7655 if {[$sha1but cget -state] == $state} return
7656 if {$state == "normal"} {
7657 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
7659 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
7663 proc gotocommit {} {
7664 global sha1string tagids headids curview varcid
7666 if {$sha1string == {}
7667 || ([info exists currentid] && $sha1string == $currentid)} return
7668 if {[info exists tagids($sha1string)]} {
7669 set id $tagids($sha1string)
7670 } elseif {[info exists headids($sha1string)]} {
7671 set id $headids($sha1string)
7673 set id [string tolower $sha1string]
7674 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7675 set matches [longid $id]
7676 if {$matches ne {}} {
7677 if {[llength $matches] > 1} {
7678 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7681 set id [lindex $matches 0]
7685 if {[commitinview $id $curview]} {
7686 selectline [rowofcommit $id] 1
7689 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7690 set msg [mc "SHA1 id %s is not known" $sha1string]
7692 set msg [mc "Tag/Head %s is not known" $sha1string]
7697 proc lineenter {x y id} {
7698 global hoverx hovery hoverid hovertimer
7699 global commitinfo canv
7701 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7705 if {[info exists hovertimer]} {
7706 after cancel $hovertimer
7708 set hovertimer [after 500 linehover]
7712 proc linemotion {x y id} {
7713 global hoverx hovery hoverid hovertimer
7715 if {[info exists hoverid] && $id == $hoverid} {
7718 if {[info exists hovertimer]} {
7719 after cancel $hovertimer
7721 set hovertimer [after 500 linehover]
7725 proc lineleave {id} {
7726 global hoverid hovertimer canv
7728 if {[info exists hoverid] && $id == $hoverid} {
7730 if {[info exists hovertimer]} {
7731 after cancel $hovertimer
7739 global hoverx hovery hoverid hovertimer
7740 global canv linespc lthickness
7743 set text [lindex $commitinfo($hoverid) 0]
7744 set ymax [lindex [$canv cget -scrollregion] 3]
7745 if {$ymax == {}} return
7746 set yfrac [lindex [$canv yview] 0]
7747 set x [expr {$hoverx + 2 * $linespc}]
7748 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7749 set x0 [expr {$x - 2 * $lthickness}]
7750 set y0 [expr {$y - 2 * $lthickness}]
7751 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7752 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7753 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7754 -fill \#ffff80 -outline black -width 1 -tags hover]
7756 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7761 proc clickisonarrow {id y} {
7764 set ranges [rowranges $id]
7765 set thresh [expr {2 * $lthickness + 6}]
7766 set n [expr {[llength $ranges] - 1}]
7767 for {set i 1} {$i < $n} {incr i} {
7768 set row [lindex $ranges $i]
7769 if {abs([yc $row] - $y) < $thresh} {
7776 proc arrowjump {id n y} {
7779 # 1 <-> 2, 3 <-> 4, etc...
7780 set n [expr {(($n - 1) ^ 1) + 1}]
7781 set row [lindex [rowranges $id] $n]
7783 set ymax [lindex [$canv cget -scrollregion] 3]
7784 if {$ymax eq {} || $ymax <= 0} return
7785 set view [$canv yview]
7786 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7787 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7791 allcanvs yview moveto $yfrac
7794 proc lineclick {x y id isnew} {
7795 global ctext commitinfo children canv thickerline curview
7797 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7802 # draw this line thicker than normal
7806 set ymax [lindex [$canv cget -scrollregion] 3]
7807 if {$ymax eq {}} return
7808 set yfrac [lindex [$canv yview] 0]
7809 set y [expr {$y + $yfrac * $ymax}]
7811 set dirn [clickisonarrow $id $y]
7813 arrowjump $id $dirn $y
7818 addtohistory [list lineclick $x $y $id 0]
7820 # fill the details pane with info about this line
7821 $ctext conf -state normal
7824 $ctext insert end "[mc "Parent"]:\t"
7825 $ctext insert end $id link0
7827 set info $commitinfo($id)
7828 $ctext insert end "\n\t[lindex $info 0]\n"
7829 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7830 set date [formatdate [lindex $info 2]]
7831 $ctext insert end "\t[mc "Date"]:\t$date\n"
7832 set kids $children($curview,$id)
7834 $ctext insert end "\n[mc "Children"]:"
7836 foreach child $kids {
7838 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7839 set info $commitinfo($child)
7840 $ctext insert end "\n\t"
7841 $ctext insert end $child link$i
7842 setlink $child link$i
7843 $ctext insert end "\n\t[lindex $info 0]"
7844 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7845 set date [formatdate [lindex $info 2]]
7846 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7849 $ctext conf -state disabled
7853 proc normalline {} {
7855 if {[info exists thickerline]} {
7864 if {[commitinview $id $curview]} {
7865 selectline [rowofcommit $id] 1
7871 if {![info exists startmstime]} {
7872 set startmstime [clock clicks -milliseconds]
7874 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7877 proc rowmenu {x y id} {
7878 global rowctxmenu selectedline rowmenuid curview
7879 global nullid nullid2 fakerowmenu mainhead
7883 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7888 if {$id ne $nullid && $id ne $nullid2} {
7889 set menu $rowctxmenu
7890 if {$mainhead ne {}} {
7891 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7893 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7896 set menu $fakerowmenu
7898 $menu entryconfigure [mca "Diff this -> selected"] -state $state
7899 $menu entryconfigure [mca "Diff selected -> this"] -state $state
7900 $menu entryconfigure [mca "Make patch"] -state $state
7901 tk_popup $menu $x $y
7904 proc diffvssel {dirn} {
7905 global rowmenuid selectedline
7907 if {$selectedline eq {}} return
7909 set oldid [commitonrow $selectedline]
7910 set newid $rowmenuid
7912 set oldid $rowmenuid
7913 set newid [commitonrow $selectedline]
7915 addtohistory [list doseldiff $oldid $newid]
7916 doseldiff $oldid $newid
7919 proc doseldiff {oldid newid} {
7923 $ctext conf -state normal
7925 init_flist [mc "Top"]
7926 $ctext insert end "[mc "From"] "
7927 $ctext insert end $oldid link0
7928 setlink $oldid link0
7929 $ctext insert end "\n "
7930 $ctext insert end [lindex $commitinfo($oldid) 0]
7931 $ctext insert end "\n\n[mc "To"] "
7932 $ctext insert end $newid link1
7933 setlink $newid link1
7934 $ctext insert end "\n "
7935 $ctext insert end [lindex $commitinfo($newid) 0]
7936 $ctext insert end "\n"
7937 $ctext conf -state disabled
7938 $ctext tag remove found 1.0 end
7939 startdiff [list $oldid $newid]
7943 global rowmenuid currentid commitinfo patchtop patchnum
7945 if {![info exists currentid]} return
7946 set oldid $currentid
7947 set oldhead [lindex $commitinfo($oldid) 0]
7948 set newid $rowmenuid
7949 set newhead [lindex $commitinfo($newid) 0]
7952 catch {destroy $top}
7954 make_transient $top .
7955 label $top.title -text [mc "Generate patch"]
7956 grid $top.title - -pady 10
7957 label $top.from -text [mc "From:"]
7958 entry $top.fromsha1 -width 40 -relief flat
7959 $top.fromsha1 insert 0 $oldid
7960 $top.fromsha1 conf -state readonly
7961 grid $top.from $top.fromsha1 -sticky w
7962 entry $top.fromhead -width 60 -relief flat
7963 $top.fromhead insert 0 $oldhead
7964 $top.fromhead conf -state readonly
7965 grid x $top.fromhead -sticky w
7966 label $top.to -text [mc "To:"]
7967 entry $top.tosha1 -width 40 -relief flat
7968 $top.tosha1 insert 0 $newid
7969 $top.tosha1 conf -state readonly
7970 grid $top.to $top.tosha1 -sticky w
7971 entry $top.tohead -width 60 -relief flat
7972 $top.tohead insert 0 $newhead
7973 $top.tohead conf -state readonly
7974 grid x $top.tohead -sticky w
7975 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7976 grid $top.rev x -pady 10
7977 label $top.flab -text [mc "Output file:"]
7978 entry $top.fname -width 60
7979 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7981 grid $top.flab $top.fname -sticky w
7983 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7984 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7985 bind $top <Key-Return> mkpatchgo
7986 bind $top <Key-Escape> mkpatchcan
7987 grid $top.buts.gen $top.buts.can
7988 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7989 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7990 grid $top.buts - -pady 10 -sticky ew
7994 proc mkpatchrev {} {
7997 set oldid [$patchtop.fromsha1 get]
7998 set oldhead [$patchtop.fromhead get]
7999 set newid [$patchtop.tosha1 get]
8000 set newhead [$patchtop.tohead get]
8001 foreach e [list fromsha1 fromhead tosha1 tohead] \
8002 v [list $newid $newhead $oldid $oldhead] {
8003 $patchtop.$e conf -state normal
8004 $patchtop.$e delete 0 end
8005 $patchtop.$e insert 0 $v
8006 $patchtop.$e conf -state readonly
8011 global patchtop nullid nullid2
8013 set oldid [$patchtop.fromsha1 get]
8014 set newid [$patchtop.tosha1 get]
8015 set fname [$patchtop.fname get]
8016 set cmd [diffcmd [list $oldid $newid] -p]
8017 # trim off the initial "|"
8018 set cmd [lrange $cmd 1 end]
8019 lappend cmd >$fname &
8020 if {[catch {eval exec $cmd} err]} {
8021 error_popup "[mc "Error creating patch:"] $err" $patchtop
8023 catch {destroy $patchtop}
8027 proc mkpatchcan {} {
8030 catch {destroy $patchtop}
8035 global rowmenuid mktagtop commitinfo
8039 catch {destroy $top}
8041 make_transient $top .
8042 label $top.title -text [mc "Create tag"]
8043 grid $top.title - -pady 10
8044 label $top.id -text [mc "ID:"]
8045 entry $top.sha1 -width 40 -relief flat
8046 $top.sha1 insert 0 $rowmenuid
8047 $top.sha1 conf -state readonly
8048 grid $top.id $top.sha1 -sticky w
8049 entry $top.head -width 60 -relief flat
8050 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8051 $top.head conf -state readonly
8052 grid x $top.head -sticky w
8053 label $top.tlab -text [mc "Tag name:"]
8054 entry $top.tag -width 60
8055 grid $top.tlab $top.tag -sticky w
8057 button $top.buts.gen -text [mc "Create"] -command mktaggo
8058 button $top.buts.can -text [mc "Cancel"] -command mktagcan
8059 bind $top <Key-Return> mktaggo
8060 bind $top <Key-Escape> mktagcan
8061 grid $top.buts.gen $top.buts.can
8062 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8063 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8064 grid $top.buts - -pady 10 -sticky ew
8069 global mktagtop env tagids idtags
8071 set id [$mktagtop.sha1 get]
8072 set tag [$mktagtop.tag get]
8074 error_popup [mc "No tag name specified"] $mktagtop
8077 if {[info exists tagids($tag)]} {
8078 error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
8082 exec git tag $tag $id
8084 error_popup "[mc "Error creating tag:"] $err" $mktagtop
8088 set tagids($tag) $id
8089 lappend idtags($id) $tag
8097 proc redrawtags {id} {
8098 global canv linehtag idpos currentid curview cmitlisted
8099 global canvxmax iddrawn circleitem mainheadid circlecolors
8101 if {![commitinview $id $curview]} return
8102 if {![info exists iddrawn($id)]} return
8103 set row [rowofcommit $id]
8104 if {$id eq $mainheadid} {
8107 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
8109 $canv itemconf $circleitem($row) -fill $ofill
8110 $canv delete tag.$id
8111 set xt [eval drawtags $id $idpos($id)]
8112 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
8113 set text [$canv itemcget $linehtag($row) -text]
8114 set font [$canv itemcget $linehtag($row) -font]
8115 set xr [expr {$xt + [font measure $font $text]}]
8116 if {$xr > $canvxmax} {
8120 if {[info exists currentid] && $currentid == $id} {
8128 catch {destroy $mktagtop}
8133 if {![domktag]} return
8137 proc writecommit {} {
8138 global rowmenuid wrcomtop commitinfo wrcomcmd
8140 set top .writecommit
8142 catch {destroy $top}
8144 make_transient $top .
8145 label $top.title -text [mc "Write commit to file"]
8146 grid $top.title - -pady 10
8147 label $top.id -text [mc "ID:"]
8148 entry $top.sha1 -width 40 -relief flat
8149 $top.sha1 insert 0 $rowmenuid
8150 $top.sha1 conf -state readonly
8151 grid $top.id $top.sha1 -sticky w
8152 entry $top.head -width 60 -relief flat
8153 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8154 $top.head conf -state readonly
8155 grid x $top.head -sticky w
8156 label $top.clab -text [mc "Command:"]
8157 entry $top.cmd -width 60 -textvariable wrcomcmd
8158 grid $top.clab $top.cmd -sticky w -pady 10
8159 label $top.flab -text [mc "Output file:"]
8160 entry $top.fname -width 60
8161 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
8162 grid $top.flab $top.fname -sticky w
8164 button $top.buts.gen -text [mc "Write"] -command wrcomgo
8165 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
8166 bind $top <Key-Return> wrcomgo
8167 bind $top <Key-Escape> wrcomcan
8168 grid $top.buts.gen $top.buts.can
8169 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8170 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8171 grid $top.buts - -pady 10 -sticky ew
8178 set id [$wrcomtop.sha1 get]
8179 set cmd "echo $id | [$wrcomtop.cmd get]"
8180 set fname [$wrcomtop.fname get]
8181 if {[catch {exec sh -c $cmd >$fname &} err]} {
8182 error_popup "[mc "Error writing commit:"] $err" $wrcomtop
8184 catch {destroy $wrcomtop}
8191 catch {destroy $wrcomtop}
8196 global rowmenuid mkbrtop
8199 catch {destroy $top}
8201 make_transient $top .
8202 label $top.title -text [mc "Create new branch"]
8203 grid $top.title - -pady 10
8204 label $top.id -text [mc "ID:"]
8205 entry $top.sha1 -width 40 -relief flat
8206 $top.sha1 insert 0 $rowmenuid
8207 $top.sha1 conf -state readonly
8208 grid $top.id $top.sha1 -sticky w
8209 label $top.nlab -text [mc "Name:"]
8210 entry $top.name -width 40
8211 bind $top.name <Key-Return> "[list mkbrgo $top]"
8212 grid $top.nlab $top.name -sticky w
8214 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
8215 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
8216 bind $top <Key-Return> [list mkbrgo $top]
8217 bind $top <Key-Escape> "catch {destroy $top}"
8218 grid $top.buts.go $top.buts.can
8219 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8220 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8221 grid $top.buts - -pady 10 -sticky ew
8226 global headids idheads
8228 set name [$top.name get]
8229 set id [$top.sha1 get]
8233 error_popup [mc "Please specify a name for the new branch"] $top
8236 if {[info exists headids($name)]} {
8237 if {![confirm_popup [mc \
8238 "Branch '%s' already exists. Overwrite?" $name] $top]} {
8241 set old_id $headids($name)
8244 catch {destroy $top}
8245 lappend cmdargs $name $id
8249 eval exec git branch $cmdargs
8255 if {$old_id ne {}} {
8261 set headids($name) $id
8262 lappend idheads($id) $name
8271 proc exec_citool {tool_args {baseid {}}} {
8272 global commitinfo env
8274 set save_env [array get env GIT_AUTHOR_*]
8276 if {$baseid ne {}} {
8277 if {![info exists commitinfo($baseid)]} {
8280 set author [lindex $commitinfo($baseid) 1]
8281 set date [lindex $commitinfo($baseid) 2]
8282 if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
8283 $author author name email]
8285 set env(GIT_AUTHOR_NAME) $name
8286 set env(GIT_AUTHOR_EMAIL) $email
8287 set env(GIT_AUTHOR_DATE) $date
8291 eval exec git citool $tool_args &
8293 array unset env GIT_AUTHOR_*
8294 array set env $save_env
8297 proc cherrypick {} {
8298 global rowmenuid curview
8299 global mainhead mainheadid
8301 set oldhead [exec git rev-parse HEAD]
8302 set dheads [descheads $rowmenuid]
8303 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
8304 set ok [confirm_popup [mc "Commit %s is already\
8305 included in branch %s -- really re-apply it?" \
8306 [string range $rowmenuid 0 7] $mainhead]]
8309 nowbusy cherrypick [mc "Cherry-picking"]
8311 # Unfortunately git-cherry-pick writes stuff to stderr even when
8312 # no error occurs, and exec takes that as an indication of error...
8313 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
8316 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
8318 error_popup [mc "Cherry-pick failed because of local changes\
8319 to file '%s'.\nPlease commit, reset or stash\
8320 your changes and try again." $fname]
8321 } elseif {[regexp -line \
8322 {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \
8324 if {[confirm_popup [mc "Cherry-pick failed because of merge\
8325 conflict.\nDo you wish to run git citool to\
8327 # Force citool to read MERGE_MSG
8328 file delete [file join [gitdir] "GITGUI_MSG"]
8329 exec_citool {} $rowmenuid
8337 set newhead [exec git rev-parse HEAD]
8338 if {$newhead eq $oldhead} {
8340 error_popup [mc "No changes committed"]
8343 addnewchild $newhead $oldhead
8344 if {[commitinview $oldhead $curview]} {
8345 insertrow $newhead $oldhead $curview
8346 if {$mainhead ne {}} {
8347 movehead $newhead $mainhead
8348 movedhead $newhead $mainhead
8350 set mainheadid $newhead
8359 global mainhead rowmenuid confirm_ok resettype
8362 set w ".confirmreset"
8365 wm title $w [mc "Confirm reset"]
8366 message $w.m -text \
8367 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
8368 -justify center -aspect 1000
8369 pack $w.m -side top -fill x -padx 20 -pady 20
8370 frame $w.f -relief sunken -border 2
8371 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
8372 grid $w.f.rt -sticky w
8374 radiobutton $w.f.soft -value soft -variable resettype -justify left \
8375 -text [mc "Soft: Leave working tree and index untouched"]
8376 grid $w.f.soft -sticky w
8377 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
8378 -text [mc "Mixed: Leave working tree untouched, reset index"]
8379 grid $w.f.mixed -sticky w
8380 radiobutton $w.f.hard -value hard -variable resettype -justify left \
8381 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
8382 grid $w.f.hard -sticky w
8383 pack $w.f -side top -fill x
8384 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
8385 pack $w.ok -side left -fill x -padx 20 -pady 20
8386 button $w.cancel -text [mc Cancel] -command "destroy $w"
8387 bind $w <Key-Escape> [list destroy $w]
8388 pack $w.cancel -side right -fill x -padx 20 -pady 20
8389 bind $w <Visibility> "grab $w; focus $w"
8391 if {!$confirm_ok} return
8392 if {[catch {set fd [open \
8393 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
8397 filerun $fd [list readresetstat $fd]
8398 nowbusy reset [mc "Resetting"]
8403 proc readresetstat {fd} {
8404 global mainhead mainheadid showlocalchanges rprogcoord
8406 if {[gets $fd line] >= 0} {
8407 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8408 set rprogcoord [expr {1.0 * $m / $n}]
8416 if {[catch {close $fd} err]} {
8419 set oldhead $mainheadid
8420 set newhead [exec git rev-parse HEAD]
8421 if {$newhead ne $oldhead} {
8422 movehead $newhead $mainhead
8423 movedhead $newhead $mainhead
8424 set mainheadid $newhead
8428 if {$showlocalchanges} {
8434 # context menu for a head
8435 proc headmenu {x y id head} {
8436 global headmenuid headmenuhead headctxmenu mainhead
8440 set headmenuhead $head
8442 if {$head eq $mainhead} {
8445 $headctxmenu entryconfigure 0 -state $state
8446 $headctxmenu entryconfigure 1 -state $state
8447 tk_popup $headctxmenu $x $y
8451 global headmenuid headmenuhead headids
8452 global showlocalchanges mainheadid
8454 # check the tree is clean first??
8455 nowbusy checkout [mc "Checking out"]
8459 set fd [open [list | git checkout $headmenuhead 2>@1] r]
8463 if {$showlocalchanges} {
8467 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
8471 proc readcheckoutstat {fd newhead newheadid} {
8472 global mainhead mainheadid headids showlocalchanges progresscoords
8474 if {[gets $fd line] >= 0} {
8475 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8476 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
8481 set progresscoords {0 0}
8484 if {[catch {close $fd} err]} {
8487 set oldmainid $mainheadid
8488 set mainhead $newhead
8489 set mainheadid $newheadid
8490 redrawtags $oldmainid
8491 redrawtags $newheadid
8493 if {$showlocalchanges} {
8499 global headmenuid headmenuhead mainhead
8502 set head $headmenuhead
8504 # this check shouldn't be needed any more...
8505 if {$head eq $mainhead} {
8506 error_popup [mc "Cannot delete the currently checked-out branch"]
8509 set dheads [descheads $id]
8510 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
8511 # the stuff on this branch isn't on any other branch
8512 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
8513 branch.\nReally delete branch %s?" $head $head]]} return
8517 if {[catch {exec git branch -D $head} err]} {
8522 removehead $id $head
8523 removedhead $id $head
8530 # Display a list of tags and heads
8532 global showrefstop bgcolor fgcolor selectbgcolor
8533 global bglist fglist reflistfilter reflist maincursor
8536 set showrefstop $top
8537 if {[winfo exists $top]} {
8543 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
8544 make_transient $top .
8545 text $top.list -background $bgcolor -foreground $fgcolor \
8546 -selectbackground $selectbgcolor -font mainfont \
8547 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
8548 -width 30 -height 20 -cursor $maincursor \
8549 -spacing1 1 -spacing3 1 -state disabled
8550 $top.list tag configure highlight -background $selectbgcolor
8551 lappend bglist $top.list
8552 lappend fglist $top.list
8553 scrollbar $top.ysb -command "$top.list yview" -orient vertical
8554 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
8555 grid $top.list $top.ysb -sticky nsew
8556 grid $top.xsb x -sticky ew
8558 label $top.f.l -text "[mc "Filter"]: "
8559 entry $top.f.e -width 20 -textvariable reflistfilter
8560 set reflistfilter "*"
8561 trace add variable reflistfilter write reflistfilter_change
8562 pack $top.f.e -side right -fill x -expand 1
8563 pack $top.f.l -side left
8564 grid $top.f - -sticky ew -pady 2
8565 button $top.close -command [list destroy $top] -text [mc "Close"]
8566 bind $top <Key-Escape> [list destroy $top]
8568 grid columnconfigure $top 0 -weight 1
8569 grid rowconfigure $top 0 -weight 1
8570 bind $top.list <1> {break}
8571 bind $top.list <B1-Motion> {break}
8572 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
8577 proc sel_reflist {w x y} {
8578 global showrefstop reflist headids tagids otherrefids
8580 if {![winfo exists $showrefstop]} return
8581 set l [lindex [split [$w index "@$x,$y"] "."] 0]
8582 set ref [lindex $reflist [expr {$l-1}]]
8583 set n [lindex $ref 0]
8584 switch -- [lindex $ref 1] {
8585 "H" {selbyid $headids($n)}
8586 "T" {selbyid $tagids($n)}
8587 "o" {selbyid $otherrefids($n)}
8589 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
8592 proc unsel_reflist {} {
8595 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8596 $showrefstop.list tag remove highlight 0.0 end
8599 proc reflistfilter_change {n1 n2 op} {
8600 global reflistfilter
8602 after cancel refill_reflist
8603 after 200 refill_reflist
8606 proc refill_reflist {} {
8607 global reflist reflistfilter showrefstop headids tagids otherrefids
8610 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8612 foreach n [array names headids] {
8613 if {[string match $reflistfilter $n]} {
8614 if {[commitinview $headids($n) $curview]} {
8615 lappend refs [list $n H]
8617 interestedin $headids($n) {run refill_reflist}
8621 foreach n [array names tagids] {
8622 if {[string match $reflistfilter $n]} {
8623 if {[commitinview $tagids($n) $curview]} {
8624 lappend refs [list $n T]
8626 interestedin $tagids($n) {run refill_reflist}
8630 foreach n [array names otherrefids] {
8631 if {[string match $reflistfilter $n]} {
8632 if {[commitinview $otherrefids($n) $curview]} {
8633 lappend refs [list $n o]
8635 interestedin $otherrefids($n) {run refill_reflist}
8639 set refs [lsort -index 0 $refs]
8640 if {$refs eq $reflist} return
8642 # Update the contents of $showrefstop.list according to the
8643 # differences between $reflist (old) and $refs (new)
8644 $showrefstop.list conf -state normal
8645 $showrefstop.list insert end "\n"
8648 while {$i < [llength $reflist] || $j < [llength $refs]} {
8649 if {$i < [llength $reflist]} {
8650 if {$j < [llength $refs]} {
8651 set cmp [string compare [lindex $reflist $i 0] \
8652 [lindex $refs $j 0]]
8654 set cmp [string compare [lindex $reflist $i 1] \
8655 [lindex $refs $j 1]]
8665 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
8673 set l [expr {$j + 1}]
8674 $showrefstop.list image create $l.0 -align baseline \
8675 -image reficon-[lindex $refs $j 1] -padx 2
8676 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
8682 # delete last newline
8683 $showrefstop.list delete end-2c end-1c
8684 $showrefstop.list conf -state disabled
8687 # Stuff for finding nearby tags
8688 proc getallcommits {} {
8689 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
8690 global idheads idtags idotherrefs allparents tagobjid
8692 if {![info exists allcommits]} {
8698 set allccache [file join [gitdir] "gitk.cache"]
8700 set f [open $allccache r]
8709 set cmd [list | git rev-list --parents]
8710 set allcupdate [expr {$seeds ne {}}]
8714 set refs [concat [array names idheads] [array names idtags] \
8715 [array names idotherrefs]]
8718 foreach name [array names tagobjid] {
8719 lappend tagobjs $tagobjid($name)
8721 foreach id [lsort -unique $refs] {
8722 if {![info exists allparents($id)] &&
8723 [lsearch -exact $tagobjs $id] < 0} {
8734 set fd [open [concat $cmd $ids] r]
8735 fconfigure $fd -blocking 0
8738 filerun $fd [list getallclines $fd]
8744 # Since most commits have 1 parent and 1 child, we group strings of
8745 # such commits into "arcs" joining branch/merge points (BMPs), which
8746 # are commits that either don't have 1 parent or don't have 1 child.
8748 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8749 # arcout(id) - outgoing arcs for BMP
8750 # arcids(a) - list of IDs on arc including end but not start
8751 # arcstart(a) - BMP ID at start of arc
8752 # arcend(a) - BMP ID at end of arc
8753 # growing(a) - arc a is still growing
8754 # arctags(a) - IDs out of arcids (excluding end) that have tags
8755 # archeads(a) - IDs out of arcids (excluding end) that have heads
8756 # The start of an arc is at the descendent end, so "incoming" means
8757 # coming from descendents, and "outgoing" means going towards ancestors.
8759 proc getallclines {fd} {
8760 global allparents allchildren idtags idheads nextarc
8761 global arcnos arcids arctags arcout arcend arcstart archeads growing
8762 global seeds allcommits cachedarcs allcupdate
8765 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8766 set id [lindex $line 0]
8767 if {[info exists allparents($id)]} {
8772 set olds [lrange $line 1 end]
8773 set allparents($id) $olds
8774 if {![info exists allchildren($id)]} {
8775 set allchildren($id) {}
8780 if {[llength $olds] == 1 && [llength $a] == 1} {
8781 lappend arcids($a) $id
8782 if {[info exists idtags($id)]} {
8783 lappend arctags($a) $id
8785 if {[info exists idheads($id)]} {
8786 lappend archeads($a) $id
8788 if {[info exists allparents($olds)]} {
8789 # seen parent already
8790 if {![info exists arcout($olds)]} {
8793 lappend arcids($a) $olds
8794 set arcend($a) $olds
8797 lappend allchildren($olds) $id
8798 lappend arcnos($olds) $a
8802 foreach a $arcnos($id) {
8803 lappend arcids($a) $id
8810 lappend allchildren($p) $id
8811 set a [incr nextarc]
8812 set arcstart($a) $id
8819 if {[info exists allparents($p)]} {
8820 # seen it already, may need to make a new branch
8821 if {![info exists arcout($p)]} {
8824 lappend arcids($a) $p
8828 lappend arcnos($p) $a
8833 global cached_dheads cached_dtags cached_atags
8834 catch {unset cached_dheads}
8835 catch {unset cached_dtags}
8836 catch {unset cached_atags}
8839 return [expr {$nid >= 1000? 2: 1}]
8843 fconfigure $fd -blocking 1
8846 # got an error reading the list of commits
8847 # if we were updating, try rereading the whole thing again
8853 error_popup "[mc "Error reading commit topology information;\
8854 branch and preceding/following tag information\
8855 will be incomplete."]\n($err)"
8858 if {[incr allcommits -1] == 0} {
8868 proc recalcarc {a} {
8869 global arctags archeads arcids idtags idheads
8873 foreach id [lrange $arcids($a) 0 end-1] {
8874 if {[info exists idtags($id)]} {
8877 if {[info exists idheads($id)]} {
8882 set archeads($a) $ah
8886 global arcnos arcids nextarc arctags archeads idtags idheads
8887 global arcstart arcend arcout allparents growing
8890 if {[llength $a] != 1} {
8891 puts "oops splitarc called but [llength $a] arcs already"
8895 set i [lsearch -exact $arcids($a) $p]
8897 puts "oops splitarc $p not in arc $a"
8900 set na [incr nextarc]
8901 if {[info exists arcend($a)]} {
8902 set arcend($na) $arcend($a)
8904 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8905 set j [lsearch -exact $arcnos($l) $a]
8906 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8908 set tail [lrange $arcids($a) [expr {$i+1}] end]
8909 set arcids($a) [lrange $arcids($a) 0 $i]
8911 set arcstart($na) $p
8913 set arcids($na) $tail
8914 if {[info exists growing($a)]} {
8920 if {[llength $arcnos($id)] == 1} {
8923 set j [lsearch -exact $arcnos($id) $a]
8924 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8928 # reconstruct tags and heads lists
8929 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8934 set archeads($na) {}
8938 # Update things for a new commit added that is a child of one
8939 # existing commit. Used when cherry-picking.
8940 proc addnewchild {id p} {
8941 global allparents allchildren idtags nextarc
8942 global arcnos arcids arctags arcout arcend arcstart archeads growing
8943 global seeds allcommits
8945 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8946 set allparents($id) [list $p]
8947 set allchildren($id) {}
8950 lappend allchildren($p) $id
8951 set a [incr nextarc]
8952 set arcstart($a) $id
8955 set arcids($a) [list $p]
8957 if {![info exists arcout($p)]} {
8960 lappend arcnos($p) $a
8961 set arcout($id) [list $a]
8964 # This implements a cache for the topology information.
8965 # The cache saves, for each arc, the start and end of the arc,
8966 # the ids on the arc, and the outgoing arcs from the end.
8967 proc readcache {f} {
8968 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8969 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8974 if {$lim - $a > 500} {
8975 set lim [expr {$a + 500}]
8979 # finish reading the cache and setting up arctags, etc.
8981 if {$line ne "1"} {error "bad final version"}
8983 foreach id [array names idtags] {
8984 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8985 [llength $allparents($id)] == 1} {
8986 set a [lindex $arcnos($id) 0]
8987 if {$arctags($a) eq {}} {
8992 foreach id [array names idheads] {
8993 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8994 [llength $allparents($id)] == 1} {
8995 set a [lindex $arcnos($id) 0]
8996 if {$archeads($a) eq {}} {
9001 foreach id [lsort -unique $possible_seeds] {
9002 if {$arcnos($id) eq {}} {
9008 while {[incr a] <= $lim} {
9010 if {[llength $line] != 3} {error "bad line"}
9011 set s [lindex $line 0]
9013 lappend arcout($s) $a
9014 if {![info exists arcnos($s)]} {
9015 lappend possible_seeds $s
9018 set e [lindex $line 1]
9023 if {![info exists arcout($e)]} {
9027 set arcids($a) [lindex $line 2]
9028 foreach id $arcids($a) {
9029 lappend allparents($s) $id
9031 lappend arcnos($id) $a
9033 if {![info exists allparents($s)]} {
9034 set allparents($s) {}
9039 set nextarc [expr {$a - 1}]
9052 global nextarc cachedarcs possible_seeds
9056 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
9057 # make sure it's an integer
9058 set cachedarcs [expr {int([lindex $line 1])}]
9059 if {$cachedarcs < 0} {error "bad number of arcs"}
9061 set possible_seeds {}
9069 proc dropcache {err} {
9070 global allcwait nextarc cachedarcs seeds
9072 #puts "dropping cache ($err)"
9073 foreach v {arcnos arcout arcids arcstart arcend growing \
9074 arctags archeads allparents allchildren} {
9085 proc writecache {f} {
9086 global cachearc cachedarcs allccache
9087 global arcstart arcend arcnos arcids arcout
9091 if {$lim - $a > 1000} {
9092 set lim [expr {$a + 1000}]
9095 while {[incr a] <= $lim} {
9096 if {[info exists arcend($a)]} {
9097 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
9099 puts $f [list $arcstart($a) {} $arcids($a)]
9104 catch {file delete $allccache}
9105 #puts "writing cache failed ($err)"
9108 set cachearc [expr {$a - 1}]
9109 if {$a > $cachedarcs} {
9118 global nextarc cachedarcs cachearc allccache
9120 if {$nextarc == $cachedarcs} return
9122 set cachedarcs $nextarc
9124 set f [open $allccache w]
9125 puts $f [list 1 $cachedarcs]
9130 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
9131 # or 0 if neither is true.
9132 proc anc_or_desc {a b} {
9133 global arcout arcstart arcend arcnos cached_isanc
9135 if {$arcnos($a) eq $arcnos($b)} {
9136 # Both are on the same arc(s); either both are the same BMP,
9137 # or if one is not a BMP, the other is also not a BMP or is
9138 # the BMP at end of the arc (and it only has 1 incoming arc).
9139 # Or both can be BMPs with no incoming arcs.
9140 if {$a eq $b || $arcnos($a) eq {}} {
9143 # assert {[llength $arcnos($a)] == 1}
9144 set arc [lindex $arcnos($a) 0]
9145 set i [lsearch -exact $arcids($arc) $a]
9146 set j [lsearch -exact $arcids($arc) $b]
9147 if {$i < 0 || $i > $j} {
9154 if {![info exists arcout($a)]} {
9155 set arc [lindex $arcnos($a) 0]
9156 if {[info exists arcend($arc)]} {
9157 set aend $arcend($arc)
9161 set a $arcstart($arc)
9165 if {![info exists arcout($b)]} {
9166 set arc [lindex $arcnos($b) 0]
9167 if {[info exists arcend($arc)]} {
9168 set bend $arcend($arc)
9172 set b $arcstart($arc)
9182 if {[info exists cached_isanc($a,$bend)]} {
9183 if {$cached_isanc($a,$bend)} {
9187 if {[info exists cached_isanc($b,$aend)]} {
9188 if {$cached_isanc($b,$aend)} {
9191 if {[info exists cached_isanc($a,$bend)]} {
9196 set todo [list $a $b]
9199 for {set i 0} {$i < [llength $todo]} {incr i} {
9200 set x [lindex $todo $i]
9201 if {$anc($x) eq {}} {
9204 foreach arc $arcnos($x) {
9205 set xd $arcstart($arc)
9207 set cached_isanc($a,$bend) 1
9208 set cached_isanc($b,$aend) 0
9210 } elseif {$xd eq $aend} {
9211 set cached_isanc($b,$aend) 1
9212 set cached_isanc($a,$bend) 0
9215 if {![info exists anc($xd)]} {
9216 set anc($xd) $anc($x)
9218 } elseif {$anc($xd) ne $anc($x)} {
9223 set cached_isanc($a,$bend) 0
9224 set cached_isanc($b,$aend) 0
9228 # This identifies whether $desc has an ancestor that is
9229 # a growing tip of the graph and which is not an ancestor of $anc
9230 # and returns 0 if so and 1 if not.
9231 # If we subsequently discover a tag on such a growing tip, and that
9232 # turns out to be a descendent of $anc (which it could, since we
9233 # don't necessarily see children before parents), then $desc
9234 # isn't a good choice to display as a descendent tag of
9235 # $anc (since it is the descendent of another tag which is
9236 # a descendent of $anc). Similarly, $anc isn't a good choice to
9237 # display as a ancestor tag of $desc.
9239 proc is_certain {desc anc} {
9240 global arcnos arcout arcstart arcend growing problems
9243 if {[llength $arcnos($anc)] == 1} {
9244 # tags on the same arc are certain
9245 if {$arcnos($desc) eq $arcnos($anc)} {
9248 if {![info exists arcout($anc)]} {
9249 # if $anc is partway along an arc, use the start of the arc instead
9250 set a [lindex $arcnos($anc) 0]
9251 set anc $arcstart($a)
9254 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
9257 set a [lindex $arcnos($desc) 0]
9263 set anclist [list $x]
9267 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
9268 set x [lindex $anclist $i]
9273 foreach a $arcout($x) {
9274 if {[info exists growing($a)]} {
9275 if {![info exists growanc($x)] && $dl($x)} {
9281 if {[info exists dl($y)]} {
9285 if {![info exists done($y)]} {
9288 if {[info exists growanc($x)]} {
9292 for {set k 0} {$k < [llength $xl]} {incr k} {
9293 set z [lindex $xl $k]
9294 foreach c $arcout($z) {
9295 if {[info exists arcend($c)]} {
9297 if {[info exists dl($v)] && $dl($v)} {
9299 if {![info exists done($v)]} {
9302 if {[info exists growanc($v)]} {
9312 } elseif {$y eq $anc || !$dl($x)} {
9323 foreach x [array names growanc] {
9332 proc validate_arctags {a} {
9333 global arctags idtags
9337 foreach id $arctags($a) {
9339 if {![info exists idtags($id)]} {
9340 set na [lreplace $na $i $i]
9347 proc validate_archeads {a} {
9348 global archeads idheads
9351 set na $archeads($a)
9352 foreach id $archeads($a) {
9354 if {![info exists idheads($id)]} {
9355 set na [lreplace $na $i $i]
9359 set archeads($a) $na
9362 # Return the list of IDs that have tags that are descendents of id,
9363 # ignoring IDs that are descendents of IDs already reported.
9364 proc desctags {id} {
9365 global arcnos arcstart arcids arctags idtags allparents
9366 global growing cached_dtags
9368 if {![info exists allparents($id)]} {
9371 set t1 [clock clicks -milliseconds]
9373 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9374 # part-way along an arc; check that arc first
9375 set a [lindex $arcnos($id) 0]
9376 if {$arctags($a) ne {}} {
9378 set i [lsearch -exact $arcids($a) $id]
9380 foreach t $arctags($a) {
9381 set j [lsearch -exact $arcids($a) $t]
9389 set id $arcstart($a)
9390 if {[info exists idtags($id)]} {
9394 if {[info exists cached_dtags($id)]} {
9395 return $cached_dtags($id)
9402 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9403 set id [lindex $todo $i]
9405 set ta [info exists hastaggedancestor($id)]
9409 # ignore tags on starting node
9410 if {!$ta && $i > 0} {
9411 if {[info exists idtags($id)]} {
9414 } elseif {[info exists cached_dtags($id)]} {
9415 set tagloc($id) $cached_dtags($id)
9419 foreach a $arcnos($id) {
9421 if {!$ta && $arctags($a) ne {}} {
9423 if {$arctags($a) ne {}} {
9424 lappend tagloc($id) [lindex $arctags($a) end]
9427 if {$ta || $arctags($a) ne {}} {
9428 set tomark [list $d]
9429 for {set j 0} {$j < [llength $tomark]} {incr j} {
9430 set dd [lindex $tomark $j]
9431 if {![info exists hastaggedancestor($dd)]} {
9432 if {[info exists done($dd)]} {
9433 foreach b $arcnos($dd) {
9434 lappend tomark $arcstart($b)
9436 if {[info exists tagloc($dd)]} {
9439 } elseif {[info exists queued($dd)]} {
9442 set hastaggedancestor($dd) 1
9446 if {![info exists queued($d)]} {
9449 if {![info exists hastaggedancestor($d)]} {
9456 foreach id [array names tagloc] {
9457 if {![info exists hastaggedancestor($id)]} {
9458 foreach t $tagloc($id) {
9459 if {[lsearch -exact $tags $t] < 0} {
9465 set t2 [clock clicks -milliseconds]
9468 # remove tags that are descendents of other tags
9469 for {set i 0} {$i < [llength $tags]} {incr i} {
9470 set a [lindex $tags $i]
9471 for {set j 0} {$j < $i} {incr j} {
9472 set b [lindex $tags $j]
9473 set r [anc_or_desc $a $b]
9475 set tags [lreplace $tags $j $j]
9478 } elseif {$r == -1} {
9479 set tags [lreplace $tags $i $i]
9486 if {[array names growing] ne {}} {
9487 # graph isn't finished, need to check if any tag could get
9488 # eclipsed by another tag coming later. Simply ignore any
9489 # tags that could later get eclipsed.
9492 if {[is_certain $t $origid]} {
9496 if {$tags eq $ctags} {
9497 set cached_dtags($origid) $tags
9502 set cached_dtags($origid) $tags
9504 set t3 [clock clicks -milliseconds]
9505 if {0 && $t3 - $t1 >= 100} {
9506 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
9507 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9513 global arcnos arcids arcout arcend arctags idtags allparents
9514 global growing cached_atags
9516 if {![info exists allparents($id)]} {
9519 set t1 [clock clicks -milliseconds]
9521 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9522 # part-way along an arc; check that arc first
9523 set a [lindex $arcnos($id) 0]
9524 if {$arctags($a) ne {}} {
9526 set i [lsearch -exact $arcids($a) $id]
9527 foreach t $arctags($a) {
9528 set j [lsearch -exact $arcids($a) $t]
9534 if {![info exists arcend($a)]} {
9538 if {[info exists idtags($id)]} {
9542 if {[info exists cached_atags($id)]} {
9543 return $cached_atags($id)
9551 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9552 set id [lindex $todo $i]
9554 set td [info exists hastaggeddescendent($id)]
9558 # ignore tags on starting node
9559 if {!$td && $i > 0} {
9560 if {[info exists idtags($id)]} {
9563 } elseif {[info exists cached_atags($id)]} {
9564 set tagloc($id) $cached_atags($id)
9568 foreach a $arcout($id) {
9569 if {!$td && $arctags($a) ne {}} {
9571 if {$arctags($a) ne {}} {
9572 lappend tagloc($id) [lindex $arctags($a) 0]
9575 if {![info exists arcend($a)]} continue
9577 if {$td || $arctags($a) ne {}} {
9578 set tomark [list $d]
9579 for {set j 0} {$j < [llength $tomark]} {incr j} {
9580 set dd [lindex $tomark $j]
9581 if {![info exists hastaggeddescendent($dd)]} {
9582 if {[info exists done($dd)]} {
9583 foreach b $arcout($dd) {
9584 if {[info exists arcend($b)]} {
9585 lappend tomark $arcend($b)
9588 if {[info exists tagloc($dd)]} {
9591 } elseif {[info exists queued($dd)]} {
9594 set hastaggeddescendent($dd) 1
9598 if {![info exists queued($d)]} {
9601 if {![info exists hastaggeddescendent($d)]} {
9607 set t2 [clock clicks -milliseconds]
9610 foreach id [array names tagloc] {
9611 if {![info exists hastaggeddescendent($id)]} {
9612 foreach t $tagloc($id) {
9613 if {[lsearch -exact $tags $t] < 0} {
9620 # remove tags that are ancestors of other tags
9621 for {set i 0} {$i < [llength $tags]} {incr i} {
9622 set a [lindex $tags $i]
9623 for {set j 0} {$j < $i} {incr j} {
9624 set b [lindex $tags $j]
9625 set r [anc_or_desc $a $b]
9627 set tags [lreplace $tags $j $j]
9630 } elseif {$r == 1} {
9631 set tags [lreplace $tags $i $i]
9638 if {[array names growing] ne {}} {
9639 # graph isn't finished, need to check if any tag could get
9640 # eclipsed by another tag coming later. Simply ignore any
9641 # tags that could later get eclipsed.
9644 if {[is_certain $origid $t]} {
9648 if {$tags eq $ctags} {
9649 set cached_atags($origid) $tags
9654 set cached_atags($origid) $tags
9656 set t3 [clock clicks -milliseconds]
9657 if {0 && $t3 - $t1 >= 100} {
9658 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
9659 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9664 # Return the list of IDs that have heads that are descendents of id,
9665 # including id itself if it has a head.
9666 proc descheads {id} {
9667 global arcnos arcstart arcids archeads idheads cached_dheads
9670 if {![info exists allparents($id)]} {
9674 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9675 # part-way along an arc; check it first
9676 set a [lindex $arcnos($id) 0]
9677 if {$archeads($a) ne {}} {
9678 validate_archeads $a
9679 set i [lsearch -exact $arcids($a) $id]
9680 foreach t $archeads($a) {
9681 set j [lsearch -exact $arcids($a) $t]
9686 set id $arcstart($a)
9692 for {set i 0} {$i < [llength $todo]} {incr i} {
9693 set id [lindex $todo $i]
9694 if {[info exists cached_dheads($id)]} {
9695 set ret [concat $ret $cached_dheads($id)]
9697 if {[info exists idheads($id)]} {
9700 foreach a $arcnos($id) {
9701 if {$archeads($a) ne {}} {
9702 validate_archeads $a
9703 if {$archeads($a) ne {}} {
9704 set ret [concat $ret $archeads($a)]
9708 if {![info exists seen($d)]} {
9715 set ret [lsort -unique $ret]
9716 set cached_dheads($origid) $ret
9717 return [concat $ret $aret]
9720 proc addedtag {id} {
9721 global arcnos arcout cached_dtags cached_atags
9723 if {![info exists arcnos($id)]} return
9724 if {![info exists arcout($id)]} {
9725 recalcarc [lindex $arcnos($id) 0]
9727 catch {unset cached_dtags}
9728 catch {unset cached_atags}
9731 proc addedhead {hid head} {
9732 global arcnos arcout cached_dheads
9734 if {![info exists arcnos($hid)]} return
9735 if {![info exists arcout($hid)]} {
9736 recalcarc [lindex $arcnos($hid) 0]
9738 catch {unset cached_dheads}
9741 proc removedhead {hid head} {
9742 global cached_dheads
9744 catch {unset cached_dheads}
9747 proc movedhead {hid head} {
9748 global arcnos arcout cached_dheads
9750 if {![info exists arcnos($hid)]} return
9751 if {![info exists arcout($hid)]} {
9752 recalcarc [lindex $arcnos($hid) 0]
9754 catch {unset cached_dheads}
9757 proc changedrefs {} {
9758 global cached_dheads cached_dtags cached_atags
9759 global arctags archeads arcnos arcout idheads idtags
9761 foreach id [concat [array names idheads] [array names idtags]] {
9762 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9763 set a [lindex $arcnos($id) 0]
9764 if {![info exists donearc($a)]} {
9770 catch {unset cached_dtags}
9771 catch {unset cached_atags}
9772 catch {unset cached_dheads}
9775 proc rereadrefs {} {
9776 global idtags idheads idotherrefs mainheadid
9778 set refids [concat [array names idtags] \
9779 [array names idheads] [array names idotherrefs]]
9780 foreach id $refids {
9781 if {![info exists ref($id)]} {
9782 set ref($id) [listrefs $id]
9785 set oldmainhead $mainheadid
9788 set refids [lsort -unique [concat $refids [array names idtags] \
9789 [array names idheads] [array names idotherrefs]]]
9790 foreach id $refids {
9791 set v [listrefs $id]
9792 if {![info exists ref($id)] || $ref($id) != $v} {
9796 if {$oldmainhead ne $mainheadid} {
9797 redrawtags $oldmainhead
9798 redrawtags $mainheadid
9803 proc listrefs {id} {
9804 global idtags idheads idotherrefs
9807 if {[info exists idtags($id)]} {
9811 if {[info exists idheads($id)]} {
9815 if {[info exists idotherrefs($id)]} {
9816 set z $idotherrefs($id)
9818 return [list $x $y $z]
9821 proc showtag {tag isnew} {
9822 global ctext tagcontents tagids linknum tagobjid
9825 addtohistory [list showtag $tag 0]
9827 $ctext conf -state normal
9831 if {![info exists tagcontents($tag)]} {
9833 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9836 if {[info exists tagcontents($tag)]} {
9837 set text $tagcontents($tag)
9839 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9841 appendwithlinks $text {}
9842 $ctext conf -state disabled
9854 if {[info exists gitktmpdir]} {
9855 catch {file delete -force $gitktmpdir}
9859 proc mkfontdisp {font top which} {
9860 global fontattr fontpref $font
9862 set fontpref($font) [set $font]
9863 button $top.${font}but -text $which -font optionfont \
9864 -command [list choosefont $font $which]
9865 label $top.$font -relief flat -font $font \
9866 -text $fontattr($font,family) -justify left
9867 grid x $top.${font}but $top.$font -sticky w
9870 proc choosefont {font which} {
9871 global fontparam fontlist fonttop fontattr
9874 set fontparam(which) $which
9875 set fontparam(font) $font
9876 set fontparam(family) [font actual $font -family]
9877 set fontparam(size) $fontattr($font,size)
9878 set fontparam(weight) $fontattr($font,weight)
9879 set fontparam(slant) $fontattr($font,slant)
9882 if {![winfo exists $top]} {
9884 eval font config sample [font actual $font]
9886 make_transient $top $prefstop
9887 wm title $top [mc "Gitk font chooser"]
9888 label $top.l -textvariable fontparam(which)
9889 pack $top.l -side top
9890 set fontlist [lsort [font families]]
9892 listbox $top.f.fam -listvariable fontlist \
9893 -yscrollcommand [list $top.f.sb set]
9894 bind $top.f.fam <<ListboxSelect>> selfontfam
9895 scrollbar $top.f.sb -command [list $top.f.fam yview]
9896 pack $top.f.sb -side right -fill y
9897 pack $top.f.fam -side left -fill both -expand 1
9898 pack $top.f -side top -fill both -expand 1
9900 spinbox $top.g.size -from 4 -to 40 -width 4 \
9901 -textvariable fontparam(size) \
9902 -validatecommand {string is integer -strict %s}
9903 checkbutton $top.g.bold -padx 5 \
9904 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9905 -variable fontparam(weight) -onvalue bold -offvalue normal
9906 checkbutton $top.g.ital -padx 5 \
9907 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9908 -variable fontparam(slant) -onvalue italic -offvalue roman
9909 pack $top.g.size $top.g.bold $top.g.ital -side left
9910 pack $top.g -side top
9911 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9913 $top.c create text 100 25 -anchor center -text $which -font sample \
9914 -fill black -tags text
9915 bind $top.c <Configure> [list centertext $top.c]
9916 pack $top.c -side top -fill x
9918 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9919 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9920 bind $top <Key-Return> fontok
9921 bind $top <Key-Escape> fontcan
9922 grid $top.buts.ok $top.buts.can
9923 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9924 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9925 pack $top.buts -side bottom -fill x
9926 trace add variable fontparam write chg_fontparam
9929 $top.c itemconf text -text $which
9931 set i [lsearch -exact $fontlist $fontparam(family)]
9933 $top.f.fam selection set $i
9938 proc centertext {w} {
9939 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9943 global fontparam fontpref prefstop
9945 set f $fontparam(font)
9946 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9947 if {$fontparam(weight) eq "bold"} {
9948 lappend fontpref($f) "bold"
9950 if {$fontparam(slant) eq "italic"} {
9951 lappend fontpref($f) "italic"
9954 $w conf -text $fontparam(family) -font $fontpref($f)
9960 global fonttop fontparam
9962 if {[info exists fonttop]} {
9963 catch {destroy $fonttop}
9964 catch {font delete sample}
9970 proc selfontfam {} {
9971 global fonttop fontparam
9973 set i [$fonttop.f.fam curselection]
9975 set fontparam(family) [$fonttop.f.fam get $i]
9979 proc chg_fontparam {v sub op} {
9982 font config sample -$sub $fontparam($sub)
9986 global maxwidth maxgraphpct
9987 global oldprefs prefstop showneartags showlocalchanges
9988 global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
9989 global tabstop limitdiffs autoselect extdifftool perfile_attrs
9993 if {[winfo exists $top]} {
9997 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9998 limitdiffs tabstop perfile_attrs} {
9999 set oldprefs($v) [set $v]
10002 wm title $top [mc "Gitk preferences"]
10003 make_transient $top .
10004 label $top.ldisp -text [mc "Commit list display options"]
10005 grid $top.ldisp - -sticky w -pady 10
10006 label $top.spacer -text " "
10007 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
10009 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
10010 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
10011 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
10013 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
10014 grid x $top.maxpctl $top.maxpct -sticky w
10015 frame $top.showlocal
10016 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
10017 checkbutton $top.showlocal.b -variable showlocalchanges
10018 pack $top.showlocal.b $top.showlocal.l -side left
10019 grid x $top.showlocal -sticky w
10020 frame $top.autoselect
10021 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
10022 checkbutton $top.autoselect.b -variable autoselect
10023 pack $top.autoselect.b $top.autoselect.l -side left
10024 grid x $top.autoselect -sticky w
10026 label $top.ddisp -text [mc "Diff display options"]
10027 grid $top.ddisp - -sticky w -pady 10
10028 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
10029 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
10030 grid x $top.tabstopl $top.tabstop -sticky w
10032 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
10033 checkbutton $top.ntag.b -variable showneartags
10034 pack $top.ntag.b $top.ntag.l -side left
10035 grid x $top.ntag -sticky w
10037 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
10038 checkbutton $top.ldiff.b -variable limitdiffs
10039 pack $top.ldiff.b $top.ldiff.l -side left
10040 grid x $top.ldiff -sticky w
10042 label $top.lattr.l -text [mc "Support per-file encodings"] -font optionfont
10043 checkbutton $top.lattr.b -variable perfile_attrs
10044 pack $top.lattr.b $top.lattr.l -side left
10045 grid x $top.lattr -sticky w
10047 entry $top.extdifft -textvariable extdifftool
10048 frame $top.extdifff
10049 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
10051 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
10052 -command choose_extdiff
10053 pack $top.extdifff.l $top.extdifff.b -side left
10054 grid x $top.extdifff $top.extdifft -sticky w
10056 label $top.cdisp -text [mc "Colors: press to choose"]
10057 grid $top.cdisp - -sticky w -pady 10
10058 label $top.bg -padx 40 -relief sunk -background $bgcolor
10059 button $top.bgbut -text [mc "Background"] -font optionfont \
10060 -command [list choosecolor bgcolor {} $top.bg background setbg]
10061 grid x $top.bgbut $top.bg -sticky w
10062 label $top.fg -padx 40 -relief sunk -background $fgcolor
10063 button $top.fgbut -text [mc "Foreground"] -font optionfont \
10064 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
10065 grid x $top.fgbut $top.fg -sticky w
10066 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
10067 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
10068 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
10069 [list $ctext tag conf d0 -foreground]]
10070 grid x $top.diffoldbut $top.diffold -sticky w
10071 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
10072 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
10073 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
10074 [list $ctext tag conf dresult -foreground]]
10075 grid x $top.diffnewbut $top.diffnew -sticky w
10076 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
10077 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
10078 -command [list choosecolor diffcolors 2 $top.hunksep \
10079 "diff hunk header" \
10080 [list $ctext tag conf hunksep -foreground]]
10081 grid x $top.hunksepbut $top.hunksep -sticky w
10082 label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
10083 button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \
10084 -command [list choosecolor markbgcolor {} $top.markbgsep \
10085 [mc "marked line background"] \
10086 [list $ctext tag conf omark -background]]
10087 grid x $top.markbgbut $top.markbgsep -sticky w
10088 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
10089 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
10090 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
10091 grid x $top.selbgbut $top.selbgsep -sticky w
10093 label $top.cfont -text [mc "Fonts: press to choose"]
10094 grid $top.cfont - -sticky w -pady 10
10095 mkfontdisp mainfont $top [mc "Main font"]
10096 mkfontdisp textfont $top [mc "Diff display font"]
10097 mkfontdisp uifont $top [mc "User interface font"]
10100 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
10101 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
10102 bind $top <Key-Return> prefsok
10103 bind $top <Key-Escape> prefscan
10104 grid $top.buts.ok $top.buts.can
10105 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10106 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10107 grid $top.buts - - -pady 10 -sticky ew
10108 bind $top <Visibility> "focus $top.buts.ok"
10111 proc choose_extdiff {} {
10114 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
10116 set extdifftool $prog
10120 proc choosecolor {v vi w x cmd} {
10123 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
10124 -title [mc "Gitk: choose color for %s" $x]]
10125 if {$c eq {}} return
10126 $w conf -background $c
10131 proc setselbg {c} {
10132 global bglist cflist
10133 foreach w $bglist {
10134 $w configure -selectbackground $c
10136 $cflist tag configure highlight \
10137 -background [$cflist cget -selectbackground]
10138 allcanvs itemconf secsel -fill $c
10144 foreach w $bglist {
10145 $w conf -background $c
10152 foreach w $fglist {
10153 $w conf -foreground $c
10155 allcanvs itemconf text -fill $c
10156 $canv itemconf circle -outline $c
10160 global oldprefs prefstop
10162 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10163 limitdiffs tabstop perfile_attrs} {
10165 set $v $oldprefs($v)
10167 catch {destroy $prefstop}
10173 global maxwidth maxgraphpct
10174 global oldprefs prefstop showneartags showlocalchanges
10175 global fontpref mainfont textfont uifont
10176 global limitdiffs treediffs perfile_attrs
10178 catch {destroy $prefstop}
10182 if {$mainfont ne $fontpref(mainfont)} {
10183 set mainfont $fontpref(mainfont)
10184 parsefont mainfont $mainfont
10185 eval font configure mainfont [fontflags mainfont]
10186 eval font configure mainfontbold [fontflags mainfont 1]
10190 if {$textfont ne $fontpref(textfont)} {
10191 set textfont $fontpref(textfont)
10192 parsefont textfont $textfont
10193 eval font configure textfont [fontflags textfont]
10194 eval font configure textfontbold [fontflags textfont 1]
10196 if {$uifont ne $fontpref(uifont)} {
10197 set uifont $fontpref(uifont)
10198 parsefont uifont $uifont
10199 eval font configure uifont [fontflags uifont]
10202 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
10203 if {$showlocalchanges} {
10209 if {$limitdiffs != $oldprefs(limitdiffs) ||
10210 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
10211 # treediffs elements are limited by path;
10212 # won't have encodings cached if perfile_attrs was just turned on
10213 catch {unset treediffs}
10215 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
10216 || $maxgraphpct != $oldprefs(maxgraphpct)} {
10218 } elseif {$showneartags != $oldprefs(showneartags) ||
10219 $limitdiffs != $oldprefs(limitdiffs)} {
10224 proc formatdate {d} {
10225 global datetimeformat
10227 set d [clock format $d -format $datetimeformat]
10232 # This list of encoding names and aliases is distilled from
10233 # http://www.iana.org/assignments/character-sets.
10234 # Not all of them are supported by Tcl.
10235 set encoding_aliases {
10236 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
10237 ISO646-US US-ASCII us IBM367 cp367 csASCII }
10238 { ISO-10646-UTF-1 csISO10646UTF1 }
10239 { ISO_646.basic:1983 ref csISO646basic1983 }
10240 { INVARIANT csINVARIANT }
10241 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
10242 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
10243 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
10244 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
10245 { NATS-DANO iso-ir-9-1 csNATSDANO }
10246 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
10247 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
10248 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
10249 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
10250 { ISO-2022-KR csISO2022KR }
10252 { ISO-2022-JP csISO2022JP }
10253 { ISO-2022-JP-2 csISO2022JP2 }
10254 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
10255 csISO13JISC6220jp }
10256 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
10257 { IT iso-ir-15 ISO646-IT csISO15Italian }
10258 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
10259 { ES iso-ir-17 ISO646-ES csISO17Spanish }
10260 { greek7-old iso-ir-18 csISO18Greek7Old }
10261 { latin-greek iso-ir-19 csISO19LatinGreek }
10262 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
10263 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
10264 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
10265 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
10266 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
10267 { BS_viewdata iso-ir-47 csISO47BSViewdata }
10268 { INIS iso-ir-49 csISO49INIS }
10269 { INIS-8 iso-ir-50 csISO50INIS8 }
10270 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
10271 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
10272 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
10273 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
10274 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
10275 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
10276 csISO60Norwegian1 }
10277 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
10278 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
10279 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
10280 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
10281 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
10282 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
10283 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
10284 { greek7 iso-ir-88 csISO88Greek7 }
10285 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
10286 { iso-ir-90 csISO90 }
10287 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
10288 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
10289 csISO92JISC62991984b }
10290 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
10291 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
10292 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
10293 csISO95JIS62291984handadd }
10294 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
10295 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
10296 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
10297 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
10298 CP819 csISOLatin1 }
10299 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
10300 { T.61-7bit iso-ir-102 csISO102T617bit }
10301 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
10302 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
10303 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
10304 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
10305 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
10306 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
10307 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
10308 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
10309 arabic csISOLatinArabic }
10310 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
10311 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
10312 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
10313 greek greek8 csISOLatinGreek }
10314 { T.101-G2 iso-ir-128 csISO128T101G2 }
10315 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
10317 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
10318 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
10319 { CSN_369103 iso-ir-139 csISO139CSN369103 }
10320 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
10321 { ISO_6937-2-add iso-ir-142 csISOTextComm }
10322 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
10323 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
10324 csISOLatinCyrillic }
10325 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
10326 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
10327 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
10328 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
10329 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
10330 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
10331 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
10332 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
10333 { ISO_10367-box iso-ir-155 csISO10367Box }
10334 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
10335 { latin-lap lap iso-ir-158 csISO158Lap }
10336 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
10337 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
10340 { JIS_X0201 X0201 csHalfWidthKatakana }
10341 { KSC5636 ISO646-KR csKSC5636 }
10342 { ISO-10646-UCS-2 csUnicode }
10343 { ISO-10646-UCS-4 csUCS4 }
10344 { DEC-MCS dec csDECMCS }
10345 { hp-roman8 roman8 r8 csHPRoman8 }
10346 { macintosh mac csMacintosh }
10347 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
10349 { IBM038 EBCDIC-INT cp038 csIBM038 }
10350 { IBM273 CP273 csIBM273 }
10351 { IBM274 EBCDIC-BE CP274 csIBM274 }
10352 { IBM275 EBCDIC-BR cp275 csIBM275 }
10353 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
10354 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
10355 { IBM280 CP280 ebcdic-cp-it csIBM280 }
10356 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
10357 { IBM284 CP284 ebcdic-cp-es csIBM284 }
10358 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
10359 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
10360 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
10361 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
10362 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
10363 { IBM424 cp424 ebcdic-cp-he csIBM424 }
10364 { IBM437 cp437 437 csPC8CodePage437 }
10365 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
10366 { IBM775 cp775 csPC775Baltic }
10367 { IBM850 cp850 850 csPC850Multilingual }
10368 { IBM851 cp851 851 csIBM851 }
10369 { IBM852 cp852 852 csPCp852 }
10370 { IBM855 cp855 855 csIBM855 }
10371 { IBM857 cp857 857 csIBM857 }
10372 { IBM860 cp860 860 csIBM860 }
10373 { IBM861 cp861 861 cp-is csIBM861 }
10374 { IBM862 cp862 862 csPC862LatinHebrew }
10375 { IBM863 cp863 863 csIBM863 }
10376 { IBM864 cp864 csIBM864 }
10377 { IBM865 cp865 865 csIBM865 }
10378 { IBM866 cp866 866 csIBM866 }
10379 { IBM868 CP868 cp-ar csIBM868 }
10380 { IBM869 cp869 869 cp-gr csIBM869 }
10381 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
10382 { IBM871 CP871 ebcdic-cp-is csIBM871 }
10383 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
10384 { IBM891 cp891 csIBM891 }
10385 { IBM903 cp903 csIBM903 }
10386 { IBM904 cp904 904 csIBBM904 }
10387 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
10388 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
10389 { IBM1026 CP1026 csIBM1026 }
10390 { EBCDIC-AT-DE csIBMEBCDICATDE }
10391 { EBCDIC-AT-DE-A csEBCDICATDEA }
10392 { EBCDIC-CA-FR csEBCDICCAFR }
10393 { EBCDIC-DK-NO csEBCDICDKNO }
10394 { EBCDIC-DK-NO-A csEBCDICDKNOA }
10395 { EBCDIC-FI-SE csEBCDICFISE }
10396 { EBCDIC-FI-SE-A csEBCDICFISEA }
10397 { EBCDIC-FR csEBCDICFR }
10398 { EBCDIC-IT csEBCDICIT }
10399 { EBCDIC-PT csEBCDICPT }
10400 { EBCDIC-ES csEBCDICES }
10401 { EBCDIC-ES-A csEBCDICESA }
10402 { EBCDIC-ES-S csEBCDICESS }
10403 { EBCDIC-UK csEBCDICUK }
10404 { EBCDIC-US csEBCDICUS }
10405 { UNKNOWN-8BIT csUnknown8BiT }
10406 { MNEMONIC csMnemonic }
10408 { VISCII csVISCII }
10411 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
10412 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
10413 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
10414 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
10415 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
10416 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
10417 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
10418 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
10419 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
10420 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
10421 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
10422 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
10423 { IBM1047 IBM-1047 }
10424 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
10425 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
10426 { UNICODE-1-1 csUnicode11 }
10427 { CESU-8 csCESU-8 }
10428 { BOCU-1 csBOCU-1 }
10429 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
10430 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
10432 { ISO-8859-15 ISO_8859-15 Latin-9 }
10433 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
10434 { GBK CP936 MS936 windows-936 }
10435 { JIS_Encoding csJISEncoding }
10436 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
10437 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
10439 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
10440 { ISO-10646-UCS-Basic csUnicodeASCII }
10441 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
10442 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
10443 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
10444 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
10445 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
10446 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
10447 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
10448 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
10449 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
10450 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
10451 { Adobe-Standard-Encoding csAdobeStandardEncoding }
10452 { Ventura-US csVenturaUS }
10453 { Ventura-International csVenturaInternational }
10454 { PC8-Danish-Norwegian csPC8DanishNorwegian }
10455 { PC8-Turkish csPC8Turkish }
10456 { IBM-Symbols csIBMSymbols }
10457 { IBM-Thai csIBMThai }
10458 { HP-Legal csHPLegal }
10459 { HP-Pi-font csHPPiFont }
10460 { HP-Math8 csHPMath8 }
10461 { Adobe-Symbol-Encoding csHPPSMath }
10462 { HP-DeskTop csHPDesktop }
10463 { Ventura-Math csVenturaMath }
10464 { Microsoft-Publishing csMicrosoftPublishing }
10465 { Windows-31J csWindows31J }
10466 { GB2312 csGB2312 }
10470 proc tcl_encoding {enc} {
10471 global encoding_aliases tcl_encoding_cache
10472 if {[info exists tcl_encoding_cache($enc)]} {
10473 return $tcl_encoding_cache($enc)
10475 set names [encoding names]
10476 set lcnames [string tolower $names]
10477 set enc [string tolower $enc]
10478 set i [lsearch -exact $lcnames $enc]
10480 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
10481 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
10482 set i [lsearch -exact $lcnames $encx]
10486 foreach l $encoding_aliases {
10487 set ll [string tolower $l]
10488 if {[lsearch -exact $ll $enc] < 0} continue
10489 # look through the aliases for one that tcl knows about
10491 set i [lsearch -exact $lcnames $e]
10493 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
10494 set i [lsearch -exact $lcnames $ex]
10504 set tclenc [lindex $names $i]
10506 set tcl_encoding_cache($enc) $tclenc
10510 proc gitattr {path attr default} {
10511 global path_attr_cache
10512 if {[info exists path_attr_cache($attr,$path)]} {
10513 set r $path_attr_cache($attr,$path)
10515 set r "unspecified"
10516 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
10517 regexp "(.*): encoding: (.*)" $line m f r
10519 set path_attr_cache($attr,$path) $r
10521 if {$r eq "unspecified"} {
10527 proc cache_gitattr {attr pathlist} {
10528 global path_attr_cache
10530 foreach path $pathlist {
10531 if {![info exists path_attr_cache($attr,$path)]} {
10532 lappend newlist $path
10536 if {[tk windowingsystem] == "win32"} {
10537 # windows has a 32k limit on the arguments to a command...
10540 while {$newlist ne {}} {
10541 set head [lrange $newlist 0 [expr {$lim - 1}]]
10542 set newlist [lrange $newlist $lim end]
10543 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
10544 foreach row [split $rlist "\n"] {
10545 if {[regexp "(.*): encoding: (.*)" $row m path value]} {
10546 if {[string index $path 0] eq "\""} {
10547 set path [encoding convertfrom [lindex $path 0]]
10549 set path_attr_cache($attr,$path) $value
10556 proc get_path_encoding {path} {
10557 global gui_encoding perfile_attrs
10558 set tcl_enc $gui_encoding
10559 if {$path ne {} && $perfile_attrs} {
10560 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
10568 # First check that Tcl/Tk is recent enough
10569 if {[catch {package require Tk 8.4} err]} {
10570 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
10571 Gitk requires at least Tcl/Tk 8.4."]
10576 set wrcomcmd "git diff-tree --stdin -p --pretty"
10580 set gitencoding [exec git config --get i18n.commitencoding]
10583 set gitencoding [exec git config --get i18n.logoutputencoding]
10585 if {$gitencoding == ""} {
10586 set gitencoding "utf-8"
10588 set tclencoding [tcl_encoding $gitencoding]
10589 if {$tclencoding == {}} {
10590 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
10593 set gui_encoding [encoding system]
10595 set enc [exec git config --get gui.encoding]
10597 set tclenc [tcl_encoding $enc]
10598 if {$tclenc ne {}} {
10599 set gui_encoding $tclenc
10601 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
10606 set mainfont {Helvetica 9}
10607 set textfont {Courier 9}
10608 set uifont {Helvetica 9 bold}
10610 set findmergefiles 0
10618 set cmitmode "patch"
10619 set wrapcomment "none"
10623 set showlocalchanges 1
10625 set datetimeformat "%Y-%m-%d %H:%M:%S"
10627 set perfile_attrs 0
10629 set extdifftool "meld"
10631 set colors {green red blue magenta darkgrey brown orange}
10634 set diffcolors {red "#00a000" blue}
10637 set selectbgcolor gray85
10638 set markbgcolor "#e0e0ff"
10640 set circlecolors {white blue gray blue blue}
10642 # button for popping up context menus
10643 if {[tk windowingsystem] eq "aqua"} {
10644 set ctxbut <Button-2>
10646 set ctxbut <Button-3>
10649 ## For msgcat loading, first locate the installation location.
10650 if { [info exists ::env(GITK_MSGSDIR)] } {
10651 ## Msgsdir was manually set in the environment.
10652 set gitk_msgsdir $::env(GITK_MSGSDIR)
10654 ## Let's guess the prefix from argv0.
10655 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
10656 set gitk_libdir [file join $gitk_prefix share gitk lib]
10657 set gitk_msgsdir [file join $gitk_libdir msgs]
10661 ## Internationalization (i18n) through msgcat and gettext. See
10662 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
10663 package require msgcat
10664 namespace import ::msgcat::mc
10665 ## And eventually load the actual message catalog
10666 ::msgcat::mcload $gitk_msgsdir
10668 catch {source ~/.gitk}
10670 font create optionfont -family sans-serif -size -12
10672 parsefont mainfont $mainfont
10673 eval font create mainfont [fontflags mainfont]
10674 eval font create mainfontbold [fontflags mainfont 1]
10676 parsefont textfont $textfont
10677 eval font create textfont [fontflags textfont]
10678 eval font create textfontbold [fontflags textfont 1]
10680 parsefont uifont $uifont
10681 eval font create uifont [fontflags uifont]
10685 # check that we can find a .git directory somewhere...
10686 if {[catch {set gitdir [gitdir]}]} {
10687 show_error {} . [mc "Cannot find a git repository here."]
10690 if {![file isdirectory $gitdir]} {
10691 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
10696 set selectheadid {}
10699 set cmdline_files {}
10701 set revtreeargscmd {}
10702 foreach arg $argv {
10703 switch -glob -- $arg {
10706 set cmdline_files [lrange $argv [expr {$i + 1}] end]
10709 "--select-commit=*" {
10710 set selecthead [string range $arg 16 end]
10713 set revtreeargscmd [string range $arg 10 end]
10716 lappend revtreeargs $arg
10722 if {$selecthead eq "HEAD"} {
10726 if {$i >= [llength $argv] && $revtreeargs ne {}} {
10727 # no -- on command line, but some arguments (other than --argscmd)
10729 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
10730 set cmdline_files [split $f "\n"]
10731 set n [llength $cmdline_files]
10732 set revtreeargs [lrange $revtreeargs 0 end-$n]
10733 # Unfortunately git rev-parse doesn't produce an error when
10734 # something is both a revision and a filename. To be consistent
10735 # with git log and git rev-list, check revtreeargs for filenames.
10736 foreach arg $revtreeargs {
10737 if {[file exists $arg]} {
10738 show_error {} . [mc "Ambiguous argument '%s': both revision\
10739 and filename" $arg]
10744 # unfortunately we get both stdout and stderr in $err,
10745 # so look for "fatal:".
10746 set i [string first "fatal:" $err]
10748 set err [string range $err [expr {$i + 6}] end]
10750 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
10755 set nullid "0000000000000000000000000000000000000000"
10756 set nullid2 "0000000000000000000000000000000000000001"
10757 set nullfile "/dev/null"
10759 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
10766 set highlight_paths {}
10768 set searchdirn -forwards
10770 set boldnamerows {}
10771 set diffelide {0 0}
10772 set markingmatches 0
10773 set linkentercount 0
10774 set need_redisplay 0
10781 set selectedhlview [mc "None"]
10782 set highlight_related [mc "None"]
10783 set highlight_files {}
10784 set viewfiles(0) {}
10787 set viewargscmd(0) {}
10789 set selectedline {}
10797 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
10800 # wait for the window to become visible
10801 tkwait visibility .
10802 wm title . "[file tail $argv0]: [file tail [pwd]]"
10805 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
10806 # create a view for the files/dirs specified on the command line
10810 set viewname(1) [mc "Command line"]
10811 set viewfiles(1) $cmdline_files
10812 set viewargs(1) $revtreeargs
10813 set viewargscmd(1) $revtreeargscmd
10817 .bar.view entryconf [mca "Edit view..."] -state normal
10818 .bar.view entryconf [mca "Delete view"] -state normal
10821 if {[info exists permviews]} {
10822 foreach v $permviews {
10825 set viewname($n) [lindex $v 0]
10826 set viewfiles($n) [lindex $v 1]
10827 set viewargs($n) [lindex $v 2]
10828 set viewargscmd($n) [lindex $v 3]