2 # Tcl ignores the next line -*- tcl -*- \
5 # Copyright © 2005-2008 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
12 if {[info exists env(GIT_DIR)]} {
15 return [exec git rev-parse --git-dir]
19 # A simple scheduler for compute-intensive stuff.
20 # The aim is to make sure that event handlers for GUI actions can
21 # run at least every 50-100 ms. Unfortunately fileevent handlers are
22 # run before X event handlers, so reading from a fast source can
23 # make the GUI completely unresponsive.
25 global isonrunq runq currunq
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {} && ![info exists currunq]} {
32 lappend runq [list {} $script]
33 set isonrunq($script) 1
36 proc filerun {fd script} {
37 fileevent $fd readable [list filereadable $fd $script]
40 proc filereadable {fd script} {
43 fileevent $fd readable {}
44 if {$runq eq {} && ![info exists currunq]} {
47 lappend runq [list $fd $script]
53 for {set i 0} {$i < [llength $runq]} {} {
54 if {[lindex $runq $i 0] eq $fd} {
55 set runq [lreplace $runq $i $i]
63 global isonrunq runq currunq
65 set tstart [clock clicks -milliseconds]
67 while {[llength $runq] > 0} {
68 set fd [lindex $runq 0 0]
69 set script [lindex $runq 0 1]
70 set currunq [lindex $runq 0]
71 set runq [lrange $runq 1 end]
72 set repeat [eval $script]
74 set t1 [clock clicks -milliseconds]
75 set t [expr {$t1 - $t0}]
76 if {$repeat ne {} && $repeat} {
77 if {$fd eq {} || $repeat == 2} {
78 # script returns 1 if it wants to be readded
79 # file readers return 2 if they could do more straight away
80 lappend runq [list $fd $script]
82 fileevent $fd readable [list filereadable $fd $script]
84 } elseif {$fd eq {}} {
85 unset isonrunq($script)
88 if {$t1 - $tstart >= 80} break
95 proc reg_instance {fd} {
96 global commfd leftover loginstance
98 set i [incr loginstance]
104 proc unmerged_files {files} {
107 # find the list of unmerged files
111 set fd [open "| git ls-files -u" r]
113 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
116 while {[gets $fd line] >= 0} {
117 set i [string first "\t" $line]
119 set fname [string range $line [expr {$i+1}] end]
120 if {[lsearch -exact $mlist $fname] >= 0} continue
122 if {$files eq {} || [path_filter $files $fname]} {
130 proc parseviewargs {n arglist} {
131 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
139 set origargs $arglist
143 foreach arg $arglist {
150 switch -glob -- $arg {
154 # remove from origargs in case we hit an unknown option
155 set origargs [lreplace $origargs $i $i]
158 # These request or affect diff output, which we don't want.
159 # Some could be used to set our defaults for diff display.
161 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
162 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
163 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
164 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
165 "--ignore-space-change" - "-U*" - "--unified=*" {
166 lappend diffargs $arg
168 # These cause our parsing of git log's output to fail, or else
169 # they're options we want to set ourselves, so ignore them.
170 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
171 "--name-only" - "--name-status" - "--color" - "--color-words" -
172 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
173 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
174 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
175 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
176 "--objects" - "--objects-edge" - "--reverse" {
178 # These are harmless, and some are even useful
179 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
180 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
181 "--full-history" - "--dense" - "--sparse" -
182 "--follow" - "--left-right" - "--encoding=*" {
185 # These mean that we get a subset of the commits
186 "--diff-filter=*" - "--no-merges" - "--unpacked" -
187 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
188 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
189 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
190 "--remove-empty" - "--first-parent" - "--cherry-pick" -
191 "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
195 # This appears to be the only one that has a value as a
196 # separate word following it
203 set notflag [expr {!$notflag}]
211 # git rev-parse doesn't understand --merge
212 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
214 # Other flag arguments including -<n>
216 if {[string is digit -strict [string range $arg 1 end]]} {
219 # a flag argument that we don't recognize;
220 # that means we can't optimize
225 # Non-flag arguments specify commits or ranges of commits
227 if {[string match "*...*" $arg]} {
228 lappend revargs --gitk-symmetric-diff-marker
234 set vdflags($n) $diffargs
235 set vflags($n) $glflags
236 set vrevs($n) $revargs
237 set vfiltered($n) $filtered
238 set vorigargs($n) $origargs
242 proc parseviewrevs {view revs} {
243 global vposids vnegids
248 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
249 # we get stdout followed by stderr in $err
250 # for an unknown rev, git rev-parse echoes it and then errors out
251 set errlines [split $err "\n"]
253 for {set l 0} {$l < [llength $errlines]} {incr l} {
254 set line [lindex $errlines $l]
255 if {!([string length $line] == 40 && [string is xdigit $line])} {
256 if {[string match "fatal:*" $line]} {
257 if {[string match "fatal: ambiguous argument*" $line]
259 if {[llength $badrev] == 1} {
260 set err "unknown revision $badrev"
262 set err "unknown revisions: [join $badrev ", "]"
265 set err [join [lrange $errlines $l end] "\n"]
272 error_popup "[mc "Error parsing revisions:"] $err"
279 foreach id [split $ids "\n"] {
280 if {$id eq "--gitk-symmetric-diff-marker"} {
282 } elseif {[string match "^*" $id]} {
289 lappend neg [string range $id 1 end]
294 lset ret end [lindex $ret end]...$id
300 set vposids($view) $pos
301 set vnegids($view) $neg
305 # Start off a git log process and arrange to read its output
306 proc start_rev_list {view} {
307 global startmsecs commitidx viewcomplete curview
309 global viewargs viewargscmd viewfiles vfilelimit
310 global showlocalchanges
311 global viewactive viewinstances vmergeonly
313 global vcanopt vflags vrevs vorigargs
315 set startmsecs [clock clicks -milliseconds]
316 set commitidx($view) 0
317 # these are set this way for the error exits
318 set viewcomplete($view) 1
319 set viewactive($view) 0
322 set args $viewargs($view)
323 if {$viewargscmd($view) ne {}} {
325 set str [exec sh -c $viewargscmd($view)]
327 error_popup "[mc "Error executing --argscmd command:"] $err"
330 set args [concat $args [split $str "\n"]]
332 set vcanopt($view) [parseviewargs $view $args]
334 set files $viewfiles($view)
335 if {$vmergeonly($view)} {
336 set files [unmerged_files $files]
339 if {$nr_unmerged == 0} {
340 error_popup [mc "No files selected: --merge specified but\
341 no files are unmerged."]
343 error_popup [mc "No files selected: --merge specified but\
344 no unmerged files are within file limit."]
349 set vfilelimit($view) $files
351 if {$vcanopt($view)} {
352 set revs [parseviewrevs $view $vrevs($view)]
356 set args [concat $vflags($view) $revs]
358 set args $vorigargs($view)
362 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
363 --boundary $args "--" $files] r]
365 error_popup "[mc "Error executing git log:"] $err"
368 set i [reg_instance $fd]
369 set viewinstances($view) [list $i]
370 if {$showlocalchanges && $mainheadid ne {}} {
371 interestedin $mainheadid dodiffindex
373 fconfigure $fd -blocking 0 -translation lf -eofchar {}
374 if {$tclencoding != {}} {
375 fconfigure $fd -encoding $tclencoding
377 filerun $fd [list getcommitlines $fd $i $view 0]
378 nowbusy $view [mc "Reading"]
379 set viewcomplete($view) 0
380 set viewactive($view) 1
384 proc stop_instance {inst} {
385 global commfd leftover
387 set fd $commfd($inst)
391 if {$::tcl_platform(platform) eq {windows}} {
400 unset leftover($inst)
403 proc stop_backends {} {
406 foreach inst [array names commfd] {
411 proc stop_rev_list {view} {
414 foreach inst $viewinstances($view) {
417 set viewinstances($view) {}
420 proc reset_pending_select {selid} {
421 global pending_select mainheadid selectheadid
424 set pending_select $selid
425 } elseif {$selectheadid ne {}} {
426 set pending_select $selectheadid
428 set pending_select $mainheadid
432 proc getcommits {selid} {
433 global canv curview need_redisplay viewactive
436 if {[start_rev_list $curview]} {
437 reset_pending_select $selid
438 show_status [mc "Reading commits..."]
441 show_status [mc "No commits selected"]
445 proc updatecommits {} {
446 global curview vcanopt vorigargs vfilelimit viewinstances
447 global viewactive viewcomplete tclencoding
448 global startmsecs showneartags showlocalchanges
449 global mainheadid pending_select
451 global varcid vposids vnegids vflags vrevs
453 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
454 set oldmainid $mainheadid
456 if {$showlocalchanges} {
457 if {$mainheadid ne $oldmainid} {
460 if {[commitinview $mainheadid $curview]} {
465 if {$vcanopt($view)} {
466 set oldpos $vposids($view)
467 set oldneg $vnegids($view)
468 set revs [parseviewrevs $view $vrevs($view)]
472 # note: getting the delta when negative refs change is hard,
473 # and could require multiple git log invocations, so in that
474 # case we ask git log for all the commits (not just the delta)
475 if {$oldneg eq $vnegids($view)} {
478 # take out positive refs that we asked for before or
479 # that we have already seen
481 if {[string length $rev] == 40} {
482 if {[lsearch -exact $oldpos $rev] < 0
483 && ![info exists varcid($view,$rev)]} {
488 lappend $newrevs $rev
491 if {$npos == 0} return
493 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
495 set args [concat $vflags($view) $revs --not $oldpos]
497 set args $vorigargs($view)
500 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
501 --boundary $args "--" $vfilelimit($view)] r]
503 error_popup "[mc "Error executing git log:"] $err"
506 if {$viewactive($view) == 0} {
507 set startmsecs [clock clicks -milliseconds]
509 set i [reg_instance $fd]
510 lappend viewinstances($view) $i
511 fconfigure $fd -blocking 0 -translation lf -eofchar {}
512 if {$tclencoding != {}} {
513 fconfigure $fd -encoding $tclencoding
515 filerun $fd [list getcommitlines $fd $i $view 1]
516 incr viewactive($view)
517 set viewcomplete($view) 0
518 reset_pending_select {}
519 nowbusy $view "Reading"
525 proc reloadcommits {} {
526 global curview viewcomplete selectedline currentid thickerline
527 global showneartags treediffs commitinterest cached_commitrow
531 if {$selectedline ne {}} {
535 if {!$viewcomplete($curview)} {
536 stop_rev_list $curview
540 catch {unset currentid}
541 catch {unset thickerline}
542 catch {unset treediffs}
549 catch {unset commitinterest}
550 catch {unset cached_commitrow}
551 catch {unset targetid}
557 # This makes a string representation of a positive integer which
558 # sorts as a string in numerical order
561 return [format "%x" $n]
562 } elseif {$n < 256} {
563 return [format "x%.2x" $n]
564 } elseif {$n < 65536} {
565 return [format "y%.4x" $n]
567 return [format "z%.8x" $n]
570 # Procedures used in reordering commits from git log (without
571 # --topo-order) into the order for display.
573 proc varcinit {view} {
574 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
575 global vtokmod varcmod vrowmod varcix vlastins
577 set varcstart($view) {{}}
578 set vupptr($view) {0}
579 set vdownptr($view) {0}
580 set vleftptr($view) {0}
581 set vbackptr($view) {0}
582 set varctok($view) {{}}
583 set varcrow($view) {{}}
584 set vtokmod($view) {}
587 set varcix($view) {{}}
588 set vlastins($view) {0}
591 proc resetvarcs {view} {
592 global varcid varccommits parents children vseedcount ordertok
594 foreach vid [array names varcid $view,*] {
599 # some commits might have children but haven't been seen yet
600 foreach vid [array names children $view,*] {
603 foreach va [array names varccommits $view,*] {
604 unset varccommits($va)
606 foreach vd [array names vseedcount $view,*] {
607 unset vseedcount($vd)
609 catch {unset ordertok}
612 # returns a list of the commits with no children
614 global vdownptr vleftptr varcstart
617 set a [lindex $vdownptr($v) 0]
619 lappend ret [lindex $varcstart($v) $a]
620 set a [lindex $vleftptr($v) $a]
625 proc newvarc {view id} {
626 global varcid varctok parents children vdatemode
627 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
628 global commitdata commitinfo vseedcount varccommits vlastins
630 set a [llength $varctok($view)]
632 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
633 if {![info exists commitinfo($id)]} {
634 parsecommit $id $commitdata($id) 1
636 set cdate [lindex $commitinfo($id) 4]
637 if {![string is integer -strict $cdate]} {
640 if {![info exists vseedcount($view,$cdate)]} {
641 set vseedcount($view,$cdate) -1
643 set c [incr vseedcount($view,$cdate)]
644 set cdate [expr {$cdate ^ 0xffffffff}]
645 set tok "s[strrep $cdate][strrep $c]"
650 if {[llength $children($vid)] > 0} {
651 set kid [lindex $children($vid) end]
652 set k $varcid($view,$kid)
653 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
656 set tok [lindex $varctok($view) $k]
660 set i [lsearch -exact $parents($view,$ki) $id]
661 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
662 append tok [strrep $j]
664 set c [lindex $vlastins($view) $ka]
665 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
667 set b [lindex $vdownptr($view) $ka]
669 set b [lindex $vleftptr($view) $c]
671 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
673 set b [lindex $vleftptr($view) $c]
676 lset vdownptr($view) $ka $a
677 lappend vbackptr($view) 0
679 lset vleftptr($view) $c $a
680 lappend vbackptr($view) $c
682 lset vlastins($view) $ka $a
683 lappend vupptr($view) $ka
684 lappend vleftptr($view) $b
686 lset vbackptr($view) $b $a
688 lappend varctok($view) $tok
689 lappend varcstart($view) $id
690 lappend vdownptr($view) 0
691 lappend varcrow($view) {}
692 lappend varcix($view) {}
693 set varccommits($view,$a) {}
694 lappend vlastins($view) 0
698 proc splitvarc {p v} {
699 global varcid varcstart varccommits varctok
700 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
702 set oa $varcid($v,$p)
703 set ac $varccommits($v,$oa)
704 set i [lsearch -exact $varccommits($v,$oa) $p]
706 set na [llength $varctok($v)]
707 # "%" sorts before "0"...
708 set tok "[lindex $varctok($v) $oa]%[strrep $i]"
709 lappend varctok($v) $tok
710 lappend varcrow($v) {}
711 lappend varcix($v) {}
712 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
713 set varccommits($v,$na) [lrange $ac $i end]
714 lappend varcstart($v) $p
715 foreach id $varccommits($v,$na) {
716 set varcid($v,$id) $na
718 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
719 lappend vlastins($v) [lindex $vlastins($v) $oa]
720 lset vdownptr($v) $oa $na
721 lset vlastins($v) $oa 0
722 lappend vupptr($v) $oa
723 lappend vleftptr($v) 0
724 lappend vbackptr($v) 0
725 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
726 lset vupptr($v) $b $na
730 proc renumbervarc {a v} {
731 global parents children varctok varcstart varccommits
732 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
734 set t1 [clock clicks -milliseconds]
740 if {[info exists isrelated($a)]} {
742 set id [lindex $varccommits($v,$a) end]
743 foreach p $parents($v,$id) {
744 if {[info exists varcid($v,$p)]} {
745 set isrelated($varcid($v,$p)) 1
750 set b [lindex $vdownptr($v) $a]
753 set b [lindex $vleftptr($v) $a]
755 set a [lindex $vupptr($v) $a]
761 if {![info exists kidchanged($a)]} continue
762 set id [lindex $varcstart($v) $a]
763 if {[llength $children($v,$id)] > 1} {
764 set children($v,$id) [lsort -command [list vtokcmp $v] \
767 set oldtok [lindex $varctok($v) $a]
768 if {!$vdatemode($v)} {
774 set kid [last_real_child $v,$id]
776 set k $varcid($v,$kid)
777 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
780 set tok [lindex $varctok($v) $k]
784 set i [lsearch -exact $parents($v,$ki) $id]
785 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
786 append tok [strrep $j]
788 if {$tok eq $oldtok} {
791 set id [lindex $varccommits($v,$a) end]
792 foreach p $parents($v,$id) {
793 if {[info exists varcid($v,$p)]} {
794 set kidchanged($varcid($v,$p)) 1
799 lset varctok($v) $a $tok
800 set b [lindex $vupptr($v) $a]
802 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
805 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
808 set c [lindex $vbackptr($v) $a]
809 set d [lindex $vleftptr($v) $a]
811 lset vdownptr($v) $b $d
813 lset vleftptr($v) $c $d
816 lset vbackptr($v) $d $c
818 if {[lindex $vlastins($v) $b] == $a} {
819 lset vlastins($v) $b $c
821 lset vupptr($v) $a $ka
822 set c [lindex $vlastins($v) $ka]
824 [string compare $tok [lindex $varctok($v) $c]] < 0} {
826 set b [lindex $vdownptr($v) $ka]
828 set b [lindex $vleftptr($v) $c]
831 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
833 set b [lindex $vleftptr($v) $c]
836 lset vdownptr($v) $ka $a
837 lset vbackptr($v) $a 0
839 lset vleftptr($v) $c $a
840 lset vbackptr($v) $a $c
842 lset vleftptr($v) $a $b
844 lset vbackptr($v) $b $a
846 lset vlastins($v) $ka $a
849 foreach id [array names sortkids] {
850 if {[llength $children($v,$id)] > 1} {
851 set children($v,$id) [lsort -command [list vtokcmp $v] \
855 set t2 [clock clicks -milliseconds]
856 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
859 # Fix up the graph after we have found out that in view $v,
860 # $p (a commit that we have already seen) is actually the parent
861 # of the last commit in arc $a.
862 proc fix_reversal {p a v} {
863 global varcid varcstart varctok vupptr
865 set pa $varcid($v,$p)
866 if {$p ne [lindex $varcstart($v) $pa]} {
868 set pa $varcid($v,$p)
870 # seeds always need to be renumbered
871 if {[lindex $vupptr($v) $pa] == 0 ||
872 [string compare [lindex $varctok($v) $a] \
873 [lindex $varctok($v) $pa]] > 0} {
878 proc insertrow {id p v} {
879 global cmitlisted children parents varcid varctok vtokmod
880 global varccommits ordertok commitidx numcommits curview
881 global targetid targetrow
885 set cmitlisted($vid) 1
886 set children($vid) {}
887 set parents($vid) [list $p]
888 set a [newvarc $v $id]
890 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
893 lappend varccommits($v,$a) $id
895 if {[llength [lappend children($vp) $id]] > 1} {
896 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
897 catch {unset ordertok}
899 fix_reversal $p $a $v
901 if {$v == $curview} {
902 set numcommits $commitidx($v)
904 if {[info exists targetid]} {
905 if {![comes_before $targetid $p]} {
912 proc insertfakerow {id p} {
913 global varcid varccommits parents children cmitlisted
914 global commitidx varctok vtokmod targetid targetrow curview numcommits
918 set i [lsearch -exact $varccommits($v,$a) $p]
920 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
923 set children($v,$id) {}
924 set parents($v,$id) [list $p]
925 set varcid($v,$id) $a
926 lappend children($v,$p) $id
927 set cmitlisted($v,$id) 1
928 set numcommits [incr commitidx($v)]
929 # note we deliberately don't update varcstart($v) even if $i == 0
930 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
932 if {[info exists targetid]} {
933 if {![comes_before $targetid $p]} {
941 proc removefakerow {id} {
942 global varcid varccommits parents children commitidx
943 global varctok vtokmod cmitlisted currentid selectedline
944 global targetid curview numcommits
947 if {[llength $parents($v,$id)] != 1} {
948 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
951 set p [lindex $parents($v,$id) 0]
952 set a $varcid($v,$id)
953 set i [lsearch -exact $varccommits($v,$a) $id]
955 puts "oops: removefakerow can't find [shortids $id] on arc $a"
959 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
960 unset parents($v,$id)
961 unset children($v,$id)
962 unset cmitlisted($v,$id)
963 set numcommits [incr commitidx($v) -1]
964 set j [lsearch -exact $children($v,$p) $id]
966 set children($v,$p) [lreplace $children($v,$p) $j $j]
969 if {[info exist currentid] && $id eq $currentid} {
973 if {[info exists targetid] && $targetid eq $id} {
980 proc first_real_child {vp} {
981 global children nullid nullid2
983 foreach id $children($vp) {
984 if {$id ne $nullid && $id ne $nullid2} {
991 proc last_real_child {vp} {
992 global children nullid nullid2
994 set kids $children($vp)
995 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
996 set id [lindex $kids $i]
997 if {$id ne $nullid && $id ne $nullid2} {
1004 proc vtokcmp {v a b} {
1005 global varctok varcid
1007 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1008 [lindex $varctok($v) $varcid($v,$b)]]
1011 # This assumes that if lim is not given, the caller has checked that
1012 # arc a's token is less than $vtokmod($v)
1013 proc modify_arc {v a {lim {}}} {
1014 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1017 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1020 set r [lindex $varcrow($v) $a]
1021 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1024 set vtokmod($v) [lindex $varctok($v) $a]
1026 if {$v == $curview} {
1027 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1028 set a [lindex $vupptr($v) $a]
1034 set lim [llength $varccommits($v,$a)]
1036 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1043 proc update_arcrows {v} {
1044 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1045 global varcid vrownum varcorder varcix varccommits
1046 global vupptr vdownptr vleftptr varctok
1047 global displayorder parentlist curview cached_commitrow
1049 if {$vrowmod($v) == $commitidx($v)} return
1050 if {$v == $curview} {
1051 if {[llength $displayorder] > $vrowmod($v)} {
1052 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1053 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1055 catch {unset cached_commitrow}
1057 set narctot [expr {[llength $varctok($v)] - 1}]
1059 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1060 # go up the tree until we find something that has a row number,
1061 # or we get to a seed
1062 set a [lindex $vupptr($v) $a]
1065 set a [lindex $vdownptr($v) 0]
1068 set varcorder($v) [list $a]
1069 lset varcix($v) $a 0
1070 lset varcrow($v) $a 0
1074 set arcn [lindex $varcix($v) $a]
1075 if {[llength $vrownum($v)] > $arcn + 1} {
1076 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1077 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1079 set row [lindex $varcrow($v) $a]
1083 incr row [llength $varccommits($v,$a)]
1084 # go down if possible
1085 set b [lindex $vdownptr($v) $a]
1087 # if not, go left, or go up until we can go left
1089 set b [lindex $vleftptr($v) $a]
1091 set a [lindex $vupptr($v) $a]
1097 lappend vrownum($v) $row
1098 lappend varcorder($v) $a
1099 lset varcix($v) $a $arcn
1100 lset varcrow($v) $a $row
1102 set vtokmod($v) [lindex $varctok($v) $p]
1104 set vrowmod($v) $row
1105 if {[info exists currentid]} {
1106 set selectedline [rowofcommit $currentid]
1110 # Test whether view $v contains commit $id
1111 proc commitinview {id v} {
1114 return [info exists varcid($v,$id)]
1117 # Return the row number for commit $id in the current view
1118 proc rowofcommit {id} {
1119 global varcid varccommits varcrow curview cached_commitrow
1120 global varctok vtokmod
1123 if {![info exists varcid($v,$id)]} {
1124 puts "oops rowofcommit no arc for [shortids $id]"
1127 set a $varcid($v,$id)
1128 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1131 if {[info exists cached_commitrow($id)]} {
1132 return $cached_commitrow($id)
1134 set i [lsearch -exact $varccommits($v,$a) $id]
1136 puts "oops didn't find commit [shortids $id] in arc $a"
1139 incr i [lindex $varcrow($v) $a]
1140 set cached_commitrow($id) $i
1144 # Returns 1 if a is on an earlier row than b, otherwise 0
1145 proc comes_before {a b} {
1146 global varcid varctok curview
1149 if {$a eq $b || ![info exists varcid($v,$a)] || \
1150 ![info exists varcid($v,$b)]} {
1153 if {$varcid($v,$a) != $varcid($v,$b)} {
1154 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1155 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1157 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1160 proc bsearch {l elt} {
1161 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1166 while {$hi - $lo > 1} {
1167 set mid [expr {int(($lo + $hi) / 2)}]
1168 set t [lindex $l $mid]
1171 } elseif {$elt > $t} {
1180 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1181 proc make_disporder {start end} {
1182 global vrownum curview commitidx displayorder parentlist
1183 global varccommits varcorder parents vrowmod varcrow
1184 global d_valid_start d_valid_end
1186 if {$end > $vrowmod($curview)} {
1187 update_arcrows $curview
1189 set ai [bsearch $vrownum($curview) $start]
1190 set start [lindex $vrownum($curview) $ai]
1191 set narc [llength $vrownum($curview)]
1192 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1193 set a [lindex $varcorder($curview) $ai]
1194 set l [llength $displayorder]
1195 set al [llength $varccommits($curview,$a)]
1196 if {$l < $r + $al} {
1198 set pad [ntimes [expr {$r - $l}] {}]
1199 set displayorder [concat $displayorder $pad]
1200 set parentlist [concat $parentlist $pad]
1201 } elseif {$l > $r} {
1202 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1203 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1205 foreach id $varccommits($curview,$a) {
1206 lappend displayorder $id
1207 lappend parentlist $parents($curview,$id)
1209 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1211 foreach id $varccommits($curview,$a) {
1212 lset displayorder $i $id
1213 lset parentlist $i $parents($curview,$id)
1221 proc commitonrow {row} {
1224 set id [lindex $displayorder $row]
1226 make_disporder $row [expr {$row + 1}]
1227 set id [lindex $displayorder $row]
1232 proc closevarcs {v} {
1233 global varctok varccommits varcid parents children
1234 global cmitlisted commitidx vtokmod
1236 set missing_parents 0
1238 set narcs [llength $varctok($v)]
1239 for {set a 1} {$a < $narcs} {incr a} {
1240 set id [lindex $varccommits($v,$a) end]
1241 foreach p $parents($v,$id) {
1242 if {[info exists varcid($v,$p)]} continue
1243 # add p as a new commit
1244 incr missing_parents
1245 set cmitlisted($v,$p) 0
1246 set parents($v,$p) {}
1247 if {[llength $children($v,$p)] == 1 &&
1248 [llength $parents($v,$id)] == 1} {
1251 set b [newvarc $v $p]
1253 set varcid($v,$p) $b
1254 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1257 lappend varccommits($v,$b) $p
1259 set scripts [check_interest $p $scripts]
1262 if {$missing_parents > 0} {
1263 foreach s $scripts {
1269 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1270 # Assumes we already have an arc for $rwid.
1271 proc rewrite_commit {v id rwid} {
1272 global children parents varcid varctok vtokmod varccommits
1274 foreach ch $children($v,$id) {
1275 # make $rwid be $ch's parent in place of $id
1276 set i [lsearch -exact $parents($v,$ch) $id]
1278 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1280 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1281 # add $ch to $rwid's children and sort the list if necessary
1282 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1283 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1284 $children($v,$rwid)]
1286 # fix the graph after joining $id to $rwid
1287 set a $varcid($v,$ch)
1288 fix_reversal $rwid $a $v
1289 # parentlist is wrong for the last element of arc $a
1290 # even if displayorder is right, hence the 3rd arg here
1291 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1295 # Mechanism for registering a command to be executed when we come
1296 # across a particular commit. To handle the case when only the
1297 # prefix of the commit is known, the commitinterest array is now
1298 # indexed by the first 4 characters of the ID. Each element is a
1299 # list of id, cmd pairs.
1300 proc interestedin {id cmd} {
1301 global commitinterest
1303 lappend commitinterest([string range $id 0 3]) $id $cmd
1306 proc check_interest {id scripts} {
1307 global commitinterest
1309 set prefix [string range $id 0 3]
1310 if {[info exists commitinterest($prefix)]} {
1312 foreach {i script} $commitinterest($prefix) {
1313 if {[string match "$i*" $id]} {
1314 lappend scripts [string map [list "%I" $id "%P" $i] $script]
1316 lappend newlist $i $script
1319 if {$newlist ne {}} {
1320 set commitinterest($prefix) $newlist
1322 unset commitinterest($prefix)
1328 proc getcommitlines {fd inst view updating} {
1329 global cmitlisted leftover
1330 global commitidx commitdata vdatemode
1331 global parents children curview hlview
1332 global idpending ordertok
1333 global varccommits varcid varctok vtokmod vfilelimit
1335 set stuff [read $fd 500000]
1336 # git log doesn't terminate the last commit with a null...
1337 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1344 global commfd viewcomplete viewactive viewname
1345 global viewinstances
1347 set i [lsearch -exact $viewinstances($view) $inst]
1349 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1351 # set it blocking so we wait for the process to terminate
1352 fconfigure $fd -blocking 1
1353 if {[catch {close $fd} err]} {
1355 if {$view != $curview} {
1356 set fv " for the \"$viewname($view)\" view"
1358 if {[string range $err 0 4] == "usage"} {
1359 set err "Gitk: error reading commits$fv:\
1360 bad arguments to git log."
1361 if {$viewname($view) eq "Command line"} {
1363 " (Note: arguments to gitk are passed to git log\
1364 to allow selection of commits to be displayed.)"
1367 set err "Error reading commits$fv: $err"
1371 if {[incr viewactive($view) -1] <= 0} {
1372 set viewcomplete($view) 1
1373 # Check if we have seen any ids listed as parents that haven't
1374 # appeared in the list
1378 if {$view == $curview} {
1387 set i [string first "\0" $stuff $start]
1389 append leftover($inst) [string range $stuff $start end]
1393 set cmit $leftover($inst)
1394 append cmit [string range $stuff 0 [expr {$i - 1}]]
1395 set leftover($inst) {}
1397 set cmit [string range $stuff $start [expr {$i - 1}]]
1399 set start [expr {$i + 1}]
1400 set j [string first "\n" $cmit]
1403 if {$j >= 0 && [string match "commit *" $cmit]} {
1404 set ids [string range $cmit 7 [expr {$j - 1}]]
1405 if {[string match {[-^<>]*} $ids]} {
1406 switch -- [string index $ids 0] {
1412 set ids [string range $ids 1 end]
1416 if {[string length $id] != 40} {
1424 if {[string length $shortcmit] > 80} {
1425 set shortcmit "[string range $shortcmit 0 80]..."
1427 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1430 set id [lindex $ids 0]
1433 if {!$listed && $updating && ![info exists varcid($vid)] &&
1434 $vfilelimit($view) ne {}} {
1435 # git log doesn't rewrite parents for unlisted commits
1436 # when doing path limiting, so work around that here
1437 # by working out the rewritten parent with git rev-list
1438 # and if we already know about it, using the rewritten
1439 # parent as a substitute parent for $id's children.
1441 set rwid [exec git rev-list --first-parent --max-count=1 \
1442 $id -- $vfilelimit($view)]
1444 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1445 # use $rwid in place of $id
1446 rewrite_commit $view $id $rwid
1453 if {[info exists varcid($vid)]} {
1454 if {$cmitlisted($vid) || !$listed} continue
1458 set olds [lrange $ids 1 end]
1462 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1463 set cmitlisted($vid) $listed
1464 set parents($vid) $olds
1465 if {![info exists children($vid)]} {
1466 set children($vid) {}
1467 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1468 set k [lindex $children($vid) 0]
1469 if {[llength $parents($view,$k)] == 1 &&
1470 (!$vdatemode($view) ||
1471 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1472 set a $varcid($view,$k)
1477 set a [newvarc $view $id]
1479 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1482 if {![info exists varcid($vid)]} {
1484 lappend varccommits($view,$a) $id
1485 incr commitidx($view)
1490 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1492 if {[llength [lappend children($vp) $id]] > 1 &&
1493 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1494 set children($vp) [lsort -command [list vtokcmp $view] \
1496 catch {unset ordertok}
1498 if {[info exists varcid($view,$p)]} {
1499 fix_reversal $p $a $view
1505 set scripts [check_interest $id $scripts]
1509 global numcommits hlview
1511 if {$view == $curview} {
1512 set numcommits $commitidx($view)
1515 if {[info exists hlview] && $view == $hlview} {
1516 # we never actually get here...
1519 foreach s $scripts {
1526 proc chewcommits {} {
1527 global curview hlview viewcomplete
1528 global pending_select
1531 if {$viewcomplete($curview)} {
1532 global commitidx varctok
1533 global numcommits startmsecs
1535 if {[info exists pending_select]} {
1537 reset_pending_select {}
1539 if {[commitinview $pending_select $curview]} {
1540 selectline [rowofcommit $pending_select] 1
1542 set row [first_real_row]
1546 if {$commitidx($curview) > 0} {
1547 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1548 #puts "overall $ms ms for $numcommits commits"
1549 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1551 show_status [mc "No commits selected"]
1558 proc readcommit {id} {
1559 if {[catch {set contents [exec git cat-file commit $id]}]} return
1560 parsecommit $id $contents 0
1563 proc parsecommit {id contents listed} {
1564 global commitinfo cdate
1573 set hdrend [string first "\n\n" $contents]
1575 # should never happen...
1576 set hdrend [string length $contents]
1578 set header [string range $contents 0 [expr {$hdrend - 1}]]
1579 set comment [string range $contents [expr {$hdrend + 2}] end]
1580 foreach line [split $header "\n"] {
1581 set tag [lindex $line 0]
1582 if {$tag == "author"} {
1583 set audate [lindex $line end-1]
1584 set auname [lrange $line 1 end-2]
1585 } elseif {$tag == "committer"} {
1586 set comdate [lindex $line end-1]
1587 set comname [lrange $line 1 end-2]
1591 # take the first non-blank line of the comment as the headline
1592 set headline [string trimleft $comment]
1593 set i [string first "\n" $headline]
1595 set headline [string range $headline 0 $i]
1597 set headline [string trimright $headline]
1598 set i [string first "\r" $headline]
1600 set headline [string trimright [string range $headline 0 $i]]
1603 # git log indents the comment by 4 spaces;
1604 # if we got this via git cat-file, add the indentation
1606 foreach line [split $comment "\n"] {
1607 append newcomment " "
1608 append newcomment $line
1609 append newcomment "\n"
1611 set comment $newcomment
1613 if {$comdate != {}} {
1614 set cdate($id) $comdate
1616 set commitinfo($id) [list $headline $auname $audate \
1617 $comname $comdate $comment]
1620 proc getcommit {id} {
1621 global commitdata commitinfo
1623 if {[info exists commitdata($id)]} {
1624 parsecommit $id $commitdata($id) 1
1627 if {![info exists commitinfo($id)]} {
1628 set commitinfo($id) [list [mc "No commit information available"]]
1634 # Expand an abbreviated commit ID to a list of full 40-char IDs that match
1635 # and are present in the current view.
1636 # This is fairly slow...
1637 proc longid {prefix} {
1638 global varcid curview
1641 foreach match [array names varcid "$curview,$prefix*"] {
1642 lappend ids [lindex [split $match ","] 1]
1648 global tagids idtags headids idheads tagobjid
1649 global otherrefids idotherrefs mainhead mainheadid
1650 global selecthead selectheadid
1652 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1655 set refd [open [list | git show-ref -d] r]
1656 while {[gets $refd line] >= 0} {
1657 if {[string index $line 40] ne " "} continue
1658 set id [string range $line 0 39]
1659 set ref [string range $line 41 end]
1660 if {![string match "refs/*" $ref]} continue
1661 set name [string range $ref 5 end]
1662 if {[string match "remotes/*" $name]} {
1663 if {![string match "*/HEAD" $name]} {
1664 set headids($name) $id
1665 lappend idheads($id) $name
1667 } elseif {[string match "heads/*" $name]} {
1668 set name [string range $name 6 end]
1669 set headids($name) $id
1670 lappend idheads($id) $name
1671 } elseif {[string match "tags/*" $name]} {
1672 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1673 # which is what we want since the former is the commit ID
1674 set name [string range $name 5 end]
1675 if {[string match "*^{}" $name]} {
1676 set name [string range $name 0 end-3]
1678 set tagobjid($name) $id
1680 set tagids($name) $id
1681 lappend idtags($id) $name
1683 set otherrefids($name) $id
1684 lappend idotherrefs($id) $name
1691 set mainheadid [exec git rev-parse HEAD]
1692 set thehead [exec git symbolic-ref HEAD]
1693 if {[string match "refs/heads/*" $thehead]} {
1694 set mainhead [string range $thehead 11 end]
1698 if {$selecthead ne {}} {
1700 set selectheadid [exec git rev-parse --verify $selecthead]
1705 # skip over fake commits
1706 proc first_real_row {} {
1707 global nullid nullid2 numcommits
1709 for {set row 0} {$row < $numcommits} {incr row} {
1710 set id [commitonrow $row]
1711 if {$id ne $nullid && $id ne $nullid2} {
1718 # update things for a head moved to a child of its previous location
1719 proc movehead {id name} {
1720 global headids idheads
1722 removehead $headids($name) $name
1723 set headids($name) $id
1724 lappend idheads($id) $name
1727 # update things when a head has been removed
1728 proc removehead {id name} {
1729 global headids idheads
1731 if {$idheads($id) eq $name} {
1734 set i [lsearch -exact $idheads($id) $name]
1736 set idheads($id) [lreplace $idheads($id) $i $i]
1739 unset headids($name)
1742 proc show_error {w top msg} {
1743 message $w.m -text $msg -justify center -aspect 400
1744 pack $w.m -side top -fill x -padx 20 -pady 20
1745 button $w.ok -text [mc OK] -command "destroy $top"
1746 pack $w.ok -side bottom -fill x
1747 bind $top <Visibility> "grab $top; focus $top"
1748 bind $top <Key-Return> "destroy $top"
1752 proc error_popup msg {
1756 show_error $w $w $msg
1759 proc confirm_popup msg {
1765 message $w.m -text $msg -justify center -aspect 400
1766 pack $w.m -side top -fill x -padx 20 -pady 20
1767 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1768 pack $w.ok -side left -fill x
1769 button $w.cancel -text [mc Cancel] -command "destroy $w"
1770 pack $w.cancel -side right -fill x
1771 bind $w <Visibility> "grab $w; focus $w"
1776 proc setoptions {} {
1777 option add *Panedwindow.showHandle 1 startupFile
1778 option add *Panedwindow.sashRelief raised startupFile
1779 option add *Button.font uifont startupFile
1780 option add *Checkbutton.font uifont startupFile
1781 option add *Radiobutton.font uifont startupFile
1782 option add *Menu.font uifont startupFile
1783 option add *Menubutton.font uifont startupFile
1784 option add *Label.font uifont startupFile
1785 option add *Message.font uifont startupFile
1786 option add *Entry.font uifont startupFile
1789 # Make a menu and submenus.
1790 # m is the window name for the menu, items is the list of menu items to add.
1791 # Each item is a list {mc label type description options...}
1792 # mc is ignored; it's so we can put mc there to alert xgettext
1793 # label is the string that appears in the menu
1794 # type is cascade, command or radiobutton (should add checkbutton)
1795 # description depends on type; it's the sublist for cascade, the
1796 # command to invoke for command, or {variable value} for radiobutton
1797 proc makemenu {m items} {
1800 set name [mc [lindex $i 1]]
1801 set type [lindex $i 2]
1802 set thing [lindex $i 3]
1803 set params [list $type]
1805 set u [string first "&" [string map {&& x} $name]]
1806 lappend params -label [string map {&& & & {}} $name]
1808 lappend params -underline $u
1813 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1814 lappend params -menu $m.$submenu
1817 lappend params -command $thing
1820 lappend params -variable [lindex $thing 0] \
1821 -value [lindex $thing 1]
1824 eval $m add $params [lrange $i 4 end]
1825 if {$type eq "cascade"} {
1826 makemenu $m.$submenu $thing
1831 # translate string and remove ampersands
1833 return [string map {&& & & {}} [mc $str]]
1836 proc makewindow {} {
1837 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1839 global findtype findtypemenu findloc findstring fstring geometry
1840 global entries sha1entry sha1string sha1but
1841 global diffcontextstring diffcontext
1843 global maincursor textcursor curtextcursor
1844 global rowctxmenu fakerowmenu mergemax wrapcomment
1845 global highlight_files gdttype
1846 global searchstring sstring
1847 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1848 global headctxmenu progresscanv progressitem progresscoords statusw
1849 global fprogitem fprogcoord lastprogupdate progupdatepending
1850 global rprogitem rprogcoord rownumsel numcommits
1853 # The "mc" arguments here are purely so that xgettext
1854 # sees the following string as needing to be translated
1856 {mc "File" cascade {
1857 {mc "Update" command updatecommits -accelerator F5}
1858 {mc "Reload" command reloadcommits}
1859 {mc "Reread references" command rereadrefs}
1860 {mc "List references" command showrefs}
1861 {mc "Quit" command doquit}
1863 {mc "Edit" cascade {
1864 {mc "Preferences" command doprefs}
1866 {mc "View" cascade {
1867 {mc "New view..." command {newview 0}}
1868 {mc "Edit view..." command editview -state disabled}
1869 {mc "Delete view" command delview -state disabled}
1871 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
1873 {mc "Help" cascade {
1874 {mc "About gitk" command about}
1875 {mc "Key bindings" command keys}
1878 . configure -menu .bar
1880 # the gui has upper and lower half, parts of a paned window.
1881 panedwindow .ctop -orient vertical
1883 # possibly use assumed geometry
1884 if {![info exists geometry(pwsash0)]} {
1885 set geometry(topheight) [expr {15 * $linespc}]
1886 set geometry(topwidth) [expr {80 * $charspc}]
1887 set geometry(botheight) [expr {15 * $linespc}]
1888 set geometry(botwidth) [expr {50 * $charspc}]
1889 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1890 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1893 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1894 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1896 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1898 # create three canvases
1899 set cscroll .tf.histframe.csb
1900 set canv .tf.histframe.pwclist.canv
1902 -selectbackground $selectbgcolor \
1903 -background $bgcolor -bd 0 \
1904 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1905 .tf.histframe.pwclist add $canv
1906 set canv2 .tf.histframe.pwclist.canv2
1908 -selectbackground $selectbgcolor \
1909 -background $bgcolor -bd 0 -yscrollincr $linespc
1910 .tf.histframe.pwclist add $canv2
1911 set canv3 .tf.histframe.pwclist.canv3
1913 -selectbackground $selectbgcolor \
1914 -background $bgcolor -bd 0 -yscrollincr $linespc
1915 .tf.histframe.pwclist add $canv3
1916 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1917 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1919 # a scroll bar to rule them
1920 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1921 pack $cscroll -side right -fill y
1922 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1923 lappend bglist $canv $canv2 $canv3
1924 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1926 # we have two button bars at bottom of top frame. Bar 1
1928 frame .tf.lbar -height 15
1930 set sha1entry .tf.bar.sha1
1931 set entries $sha1entry
1932 set sha1but .tf.bar.sha1label
1933 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1934 -command gotocommit -width 8
1935 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1936 pack .tf.bar.sha1label -side left
1937 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1938 trace add variable sha1string write sha1change
1939 pack $sha1entry -side left -pady 2
1941 image create bitmap bm-left -data {
1942 #define left_width 16
1943 #define left_height 16
1944 static unsigned char left_bits[] = {
1945 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1946 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1947 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1949 image create bitmap bm-right -data {
1950 #define right_width 16
1951 #define right_height 16
1952 static unsigned char right_bits[] = {
1953 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1954 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1955 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1957 button .tf.bar.leftbut -image bm-left -command goback \
1958 -state disabled -width 26
1959 pack .tf.bar.leftbut -side left -fill y
1960 button .tf.bar.rightbut -image bm-right -command goforw \
1961 -state disabled -width 26
1962 pack .tf.bar.rightbut -side left -fill y
1964 label .tf.bar.rowlabel -text [mc "Row"]
1966 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
1967 -relief sunken -anchor e
1968 label .tf.bar.rowlabel2 -text "/"
1969 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
1970 -relief sunken -anchor e
1971 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
1974 trace add variable selectedline write selectedline_change
1976 # Status label and progress bar
1977 set statusw .tf.bar.status
1978 label $statusw -width 15 -relief sunken
1979 pack $statusw -side left -padx 5
1980 set h [expr {[font metrics uifont -linespace] + 2}]
1981 set progresscanv .tf.bar.progress
1982 canvas $progresscanv -relief sunken -height $h -borderwidth 2
1983 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1984 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1985 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1986 pack $progresscanv -side right -expand 1 -fill x
1987 set progresscoords {0 0}
1990 bind $progresscanv <Configure> adjustprogress
1991 set lastprogupdate [clock clicks -milliseconds]
1992 set progupdatepending 0
1994 # build up the bottom bar of upper window
1995 label .tf.lbar.flabel -text "[mc "Find"] "
1996 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
1997 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
1998 label .tf.lbar.flab2 -text " [mc "commit"] "
1999 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2001 set gdttype [mc "containing:"]
2002 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
2003 [mc "containing:"] \
2004 [mc "touching paths:"] \
2005 [mc "adding/removing string:"]]
2006 trace add variable gdttype write gdttype_change
2007 pack .tf.lbar.gdttype -side left -fill y
2010 set fstring .tf.lbar.findstring
2011 lappend entries $fstring
2012 entry $fstring -width 30 -font textfont -textvariable findstring
2013 trace add variable findstring write find_change
2014 set findtype [mc "Exact"]
2015 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
2016 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2017 trace add variable findtype write findcom_change
2018 set findloc [mc "All fields"]
2019 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2020 [mc "Comments"] [mc "Author"] [mc "Committer"]
2021 trace add variable findloc write find_change
2022 pack .tf.lbar.findloc -side right
2023 pack .tf.lbar.findtype -side right
2024 pack $fstring -side left -expand 1 -fill x
2026 # Finish putting the upper half of the viewer together
2027 pack .tf.lbar -in .tf -side bottom -fill x
2028 pack .tf.bar -in .tf -side bottom -fill x
2029 pack .tf.histframe -fill both -side top -expand 1
2031 .ctop paneconfigure .tf -height $geometry(topheight)
2032 .ctop paneconfigure .tf -width $geometry(topwidth)
2034 # now build up the bottom
2035 panedwindow .pwbottom -orient horizontal
2037 # lower left, a text box over search bar, scroll bar to the right
2038 # if we know window height, then that will set the lower text height, otherwise
2039 # we set lower text height which will drive window height
2040 if {[info exists geometry(main)]} {
2041 frame .bleft -width $geometry(botwidth)
2043 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2049 button .bleft.top.search -text [mc "Search"] -command dosearch
2050 pack .bleft.top.search -side left -padx 5
2051 set sstring .bleft.top.sstring
2052 entry $sstring -width 20 -font textfont -textvariable searchstring
2053 lappend entries $sstring
2054 trace add variable searchstring write incrsearch
2055 pack $sstring -side left -expand 1 -fill x
2056 radiobutton .bleft.mid.diff -text [mc "Diff"] \
2057 -command changediffdisp -variable diffelide -value {0 0}
2058 radiobutton .bleft.mid.old -text [mc "Old version"] \
2059 -command changediffdisp -variable diffelide -value {0 1}
2060 radiobutton .bleft.mid.new -text [mc "New version"] \
2061 -command changediffdisp -variable diffelide -value {1 0}
2062 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2063 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2064 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
2065 -from 1 -increment 1 -to 10000000 \
2066 -validate all -validatecommand "diffcontextvalidate %P" \
2067 -textvariable diffcontextstring
2068 .bleft.mid.diffcontext set $diffcontext
2069 trace add variable diffcontextstring write diffcontextchange
2070 lappend entries .bleft.mid.diffcontext
2071 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2072 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2073 -command changeignorespace -variable ignorespace
2074 pack .bleft.mid.ignspace -side left -padx 5
2075 set ctext .bleft.bottom.ctext
2076 text $ctext -background $bgcolor -foreground $fgcolor \
2077 -state disabled -font textfont \
2078 -yscrollcommand scrolltext -wrap none \
2079 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2081 $ctext conf -tabstyle wordprocessor
2083 scrollbar .bleft.bottom.sb -command "$ctext yview"
2084 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
2086 pack .bleft.top -side top -fill x
2087 pack .bleft.mid -side top -fill x
2088 grid $ctext .bleft.bottom.sb -sticky nsew
2089 grid .bleft.bottom.sbhorizontal -sticky ew
2090 grid columnconfigure .bleft.bottom 0 -weight 1
2091 grid rowconfigure .bleft.bottom 0 -weight 1
2092 grid rowconfigure .bleft.bottom 1 -weight 0
2093 pack .bleft.bottom -side top -fill both -expand 1
2094 lappend bglist $ctext
2095 lappend fglist $ctext
2097 $ctext tag conf comment -wrap $wrapcomment
2098 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2099 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2100 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2101 $ctext tag conf d1 -fore [lindex $diffcolors 1]
2102 $ctext tag conf m0 -fore red
2103 $ctext tag conf m1 -fore blue
2104 $ctext tag conf m2 -fore green
2105 $ctext tag conf m3 -fore purple
2106 $ctext tag conf m4 -fore brown
2107 $ctext tag conf m5 -fore "#009090"
2108 $ctext tag conf m6 -fore magenta
2109 $ctext tag conf m7 -fore "#808000"
2110 $ctext tag conf m8 -fore "#009000"
2111 $ctext tag conf m9 -fore "#ff0080"
2112 $ctext tag conf m10 -fore cyan
2113 $ctext tag conf m11 -fore "#b07070"
2114 $ctext tag conf m12 -fore "#70b0f0"
2115 $ctext tag conf m13 -fore "#70f0b0"
2116 $ctext tag conf m14 -fore "#f0b070"
2117 $ctext tag conf m15 -fore "#ff70b0"
2118 $ctext tag conf mmax -fore darkgrey
2120 $ctext tag conf mresult -font textfontbold
2121 $ctext tag conf msep -font textfontbold
2122 $ctext tag conf found -back yellow
2124 .pwbottom add .bleft
2125 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2130 radiobutton .bright.mode.patch -text [mc "Patch"] \
2131 -command reselectline -variable cmitmode -value "patch"
2132 radiobutton .bright.mode.tree -text [mc "Tree"] \
2133 -command reselectline -variable cmitmode -value "tree"
2134 grid .bright.mode.patch .bright.mode.tree -sticky ew
2135 pack .bright.mode -side top -fill x
2136 set cflist .bright.cfiles
2137 set indent [font measure mainfont "nn"]
2139 -selectbackground $selectbgcolor \
2140 -background $bgcolor -foreground $fgcolor \
2142 -tabs [list $indent [expr {2 * $indent}]] \
2143 -yscrollcommand ".bright.sb set" \
2144 -cursor [. cget -cursor] \
2145 -spacing1 1 -spacing3 1
2146 lappend bglist $cflist
2147 lappend fglist $cflist
2148 scrollbar .bright.sb -command "$cflist yview"
2149 pack .bright.sb -side right -fill y
2150 pack $cflist -side left -fill both -expand 1
2151 $cflist tag configure highlight \
2152 -background [$cflist cget -selectbackground]
2153 $cflist tag configure bold -font mainfontbold
2155 .pwbottom add .bright
2158 # restore window width & height if known
2159 if {[info exists geometry(main)]} {
2160 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2161 if {$w > [winfo screenwidth .]} {
2162 set w [winfo screenwidth .]
2164 if {$h > [winfo screenheight .]} {
2165 set h [winfo screenheight .]
2167 wm geometry . "${w}x$h"
2171 if {[tk windowingsystem] eq {aqua}} {
2177 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2178 pack .ctop -fill both -expand 1
2179 bindall <1> {selcanvline %W %x %y}
2180 #bindall <B1-Motion> {selcanvline %W %x %y}
2181 if {[tk windowingsystem] == "win32"} {
2182 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2183 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2185 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2186 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2187 if {[tk windowingsystem] eq "aqua"} {
2188 bindall <MouseWheel> {
2189 set delta [expr {- (%D)}]
2190 allcanvs yview scroll $delta units
2194 bindall <2> "canvscan mark %W %x %y"
2195 bindall <B2-Motion> "canvscan dragto %W %x %y"
2196 bindkey <Home> selfirstline
2197 bindkey <End> sellastline
2198 bind . <Key-Up> "selnextline -1"
2199 bind . <Key-Down> "selnextline 1"
2200 bind . <Shift-Key-Up> "dofind -1 0"
2201 bind . <Shift-Key-Down> "dofind 1 0"
2202 bindkey <Key-Right> "goforw"
2203 bindkey <Key-Left> "goback"
2204 bind . <Key-Prior> "selnextpage -1"
2205 bind . <Key-Next> "selnextpage 1"
2206 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2207 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2208 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2209 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2210 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2211 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2212 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2213 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2214 bindkey <Key-space> "$ctext yview scroll 1 pages"
2215 bindkey p "selnextline -1"
2216 bindkey n "selnextline 1"
2219 bindkey i "selnextline -1"
2220 bindkey k "selnextline 1"
2224 bindkey d "$ctext yview scroll 18 units"
2225 bindkey u "$ctext yview scroll -18 units"
2226 bindkey / {dofind 1 1}
2227 bindkey <Key-Return> {dofind 1 1}
2228 bindkey ? {dofind -1 1}
2230 bindkey <F5> updatecommits
2231 bind . <$M1B-q> doquit
2232 bind . <$M1B-f> {dofind 1 1}
2233 bind . <$M1B-g> {dofind 1 0}
2234 bind . <$M1B-r> dosearchback
2235 bind . <$M1B-s> dosearch
2236 bind . <$M1B-equal> {incrfont 1}
2237 bind . <$M1B-plus> {incrfont 1}
2238 bind . <$M1B-KP_Add> {incrfont 1}
2239 bind . <$M1B-minus> {incrfont -1}
2240 bind . <$M1B-KP_Subtract> {incrfont -1}
2241 wm protocol . WM_DELETE_WINDOW doquit
2242 bind . <Destroy> {stop_backends}
2243 bind . <Button-1> "click %W"
2244 bind $fstring <Key-Return> {dofind 1 1}
2245 bind $sha1entry <Key-Return> {gotocommit; break}
2246 bind $sha1entry <<PasteSelection>> clearsha1
2247 bind $cflist <1> {sel_flist %W %x %y; break}
2248 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2249 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2251 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2252 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2254 set maincursor [. cget -cursor]
2255 set textcursor [$ctext cget -cursor]
2256 set curtextcursor $textcursor
2258 set rowctxmenu .rowctxmenu
2259 makemenu $rowctxmenu {
2260 {mc "Diff this -> selected" command {diffvssel 0}}
2261 {mc "Diff selected -> this" command {diffvssel 1}}
2262 {mc "Make patch" command mkpatch}
2263 {mc "Create tag" command mktag}
2264 {mc "Write commit to file" command writecommit}
2265 {mc "Create new branch" command mkbranch}
2266 {mc "Cherry-pick this commit" command cherrypick}
2267 {mc "Reset HEAD branch to here" command resethead}
2269 $rowctxmenu configure -tearoff 0
2271 set fakerowmenu .fakerowmenu
2272 makemenu $fakerowmenu {
2273 {mc "Diff this -> selected" command {diffvssel 0}}
2274 {mc "Diff selected -> this" command {diffvssel 1}}
2275 {mc "Make patch" command mkpatch}
2277 $fakerowmenu configure -tearoff 0
2279 set headctxmenu .headctxmenu
2280 makemenu $headctxmenu {
2281 {mc "Check out this branch" command cobranch}
2282 {mc "Remove this branch" command rmbranch}
2284 $headctxmenu configure -tearoff 0
2287 set flist_menu .flistctxmenu
2288 makemenu $flist_menu {
2289 {mc "Highlight this too" command {flist_hl 0}}
2290 {mc "Highlight this only" command {flist_hl 1}}
2291 {mc "External diff" command {external_diff}}
2292 {mc "Blame parent commit" command {external_blame 1}}
2294 $flist_menu configure -tearoff 0
2297 set diff_menu .diffctxmenu
2298 makemenu $diff_menu {
2299 {mc "Show origin of this line" command show_line_source}
2300 {mc "Run git gui blame on this line" command {external_blame_diff}}
2302 $diff_menu configure -tearoff 0
2305 # Windows sends all mouse wheel events to the current focused window, not
2306 # the one where the mouse hovers, so bind those events here and redirect
2307 # to the correct window
2308 proc windows_mousewheel_redirector {W X Y D} {
2309 global canv canv2 canv3
2310 set w [winfo containing -displayof $W $X $Y]
2312 set u [expr {$D < 0 ? 5 : -5}]
2313 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2314 allcanvs yview scroll $u units
2317 $w yview scroll $u units
2323 # Update row number label when selectedline changes
2324 proc selectedline_change {n1 n2 op} {
2325 global selectedline rownumsel
2327 if {$selectedline eq {}} {
2330 set rownumsel [expr {$selectedline + 1}]
2334 # mouse-2 makes all windows scan vertically, but only the one
2335 # the cursor is in scans horizontally
2336 proc canvscan {op w x y} {
2337 global canv canv2 canv3
2338 foreach c [list $canv $canv2 $canv3] {
2347 proc scrollcanv {cscroll f0 f1} {
2348 $cscroll set $f0 $f1
2353 # when we make a key binding for the toplevel, make sure
2354 # it doesn't get triggered when that key is pressed in the
2355 # find string entry widget.
2356 proc bindkey {ev script} {
2359 set escript [bind Entry $ev]
2360 if {$escript == {}} {
2361 set escript [bind Entry <Key>]
2363 foreach e $entries {
2364 bind $e $ev "$escript; break"
2368 # set the focus back to the toplevel for any click outside
2371 global ctext entries
2372 foreach e [concat $entries $ctext] {
2373 if {$w == $e} return
2378 # Adjust the progress bar for a change in requested extent or canvas size
2379 proc adjustprogress {} {
2380 global progresscanv progressitem progresscoords
2381 global fprogitem fprogcoord lastprogupdate progupdatepending
2382 global rprogitem rprogcoord
2384 set w [expr {[winfo width $progresscanv] - 4}]
2385 set x0 [expr {$w * [lindex $progresscoords 0]}]
2386 set x1 [expr {$w * [lindex $progresscoords 1]}]
2387 set h [winfo height $progresscanv]
2388 $progresscanv coords $progressitem $x0 0 $x1 $h
2389 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2390 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2391 set now [clock clicks -milliseconds]
2392 if {$now >= $lastprogupdate + 100} {
2393 set progupdatepending 0
2395 } elseif {!$progupdatepending} {
2396 set progupdatepending 1
2397 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2401 proc doprogupdate {} {
2402 global lastprogupdate progupdatepending
2404 if {$progupdatepending} {
2405 set progupdatepending 0
2406 set lastprogupdate [clock clicks -milliseconds]
2411 proc savestuff {w} {
2412 global canv canv2 canv3 mainfont textfont uifont tabstop
2413 global stuffsaved findmergefiles maxgraphpct
2414 global maxwidth showneartags showlocalchanges
2415 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2416 global cmitmode wrapcomment datetimeformat limitdiffs
2417 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2418 global autoselect extdifftool perfile_attrs markbgcolor
2420 if {$stuffsaved} return
2421 if {![winfo viewable .]} return
2423 set f [open "~/.gitk-new" w]
2424 puts $f [list set mainfont $mainfont]
2425 puts $f [list set textfont $textfont]
2426 puts $f [list set uifont $uifont]
2427 puts $f [list set tabstop $tabstop]
2428 puts $f [list set findmergefiles $findmergefiles]
2429 puts $f [list set maxgraphpct $maxgraphpct]
2430 puts $f [list set maxwidth $maxwidth]
2431 puts $f [list set cmitmode $cmitmode]
2432 puts $f [list set wrapcomment $wrapcomment]
2433 puts $f [list set autoselect $autoselect]
2434 puts $f [list set showneartags $showneartags]
2435 puts $f [list set showlocalchanges $showlocalchanges]
2436 puts $f [list set datetimeformat $datetimeformat]
2437 puts $f [list set limitdiffs $limitdiffs]
2438 puts $f [list set bgcolor $bgcolor]
2439 puts $f [list set fgcolor $fgcolor]
2440 puts $f [list set colors $colors]
2441 puts $f [list set diffcolors $diffcolors]
2442 puts $f [list set markbgcolor $markbgcolor]
2443 puts $f [list set diffcontext $diffcontext]
2444 puts $f [list set selectbgcolor $selectbgcolor]
2445 puts $f [list set extdifftool $extdifftool]
2446 puts $f [list set perfile_attrs $perfile_attrs]
2448 puts $f "set geometry(main) [wm geometry .]"
2449 puts $f "set geometry(topwidth) [winfo width .tf]"
2450 puts $f "set geometry(topheight) [winfo height .tf]"
2451 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2452 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2453 puts $f "set geometry(botwidth) [winfo width .bleft]"
2454 puts $f "set geometry(botheight) [winfo height .bleft]"
2456 puts -nonewline $f "set permviews {"
2457 for {set v 0} {$v < $nextviewnum} {incr v} {
2458 if {$viewperm($v)} {
2459 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2464 file rename -force "~/.gitk-new" "~/.gitk"
2469 proc resizeclistpanes {win w} {
2471 if {[info exists oldwidth($win)]} {
2472 set s0 [$win sash coord 0]
2473 set s1 [$win sash coord 1]
2475 set sash0 [expr {int($w/2 - 2)}]
2476 set sash1 [expr {int($w*5/6 - 2)}]
2478 set factor [expr {1.0 * $w / $oldwidth($win)}]
2479 set sash0 [expr {int($factor * [lindex $s0 0])}]
2480 set sash1 [expr {int($factor * [lindex $s1 0])}]
2484 if {$sash1 < $sash0 + 20} {
2485 set sash1 [expr {$sash0 + 20}]
2487 if {$sash1 > $w - 10} {
2488 set sash1 [expr {$w - 10}]
2489 if {$sash0 > $sash1 - 20} {
2490 set sash0 [expr {$sash1 - 20}]
2494 $win sash place 0 $sash0 [lindex $s0 1]
2495 $win sash place 1 $sash1 [lindex $s1 1]
2497 set oldwidth($win) $w
2500 proc resizecdetpanes {win w} {
2502 if {[info exists oldwidth($win)]} {
2503 set s0 [$win sash coord 0]
2505 set sash0 [expr {int($w*3/4 - 2)}]
2507 set factor [expr {1.0 * $w / $oldwidth($win)}]
2508 set sash0 [expr {int($factor * [lindex $s0 0])}]
2512 if {$sash0 > $w - 15} {
2513 set sash0 [expr {$w - 15}]
2516 $win sash place 0 $sash0 [lindex $s0 1]
2518 set oldwidth($win) $w
2521 proc allcanvs args {
2522 global canv canv2 canv3
2528 proc bindall {event action} {
2529 global canv canv2 canv3
2530 bind $canv $event $action
2531 bind $canv2 $event $action
2532 bind $canv3 $event $action
2538 if {[winfo exists $w]} {
2543 wm title $w [mc "About gitk"]
2544 message $w.m -text [mc "
2545 Gitk - a commit viewer for git
2547 Copyright © 2005-2008 Paul Mackerras
2549 Use and redistribute under the terms of the GNU General Public License"] \
2550 -justify center -aspect 400 -border 2 -bg white -relief groove
2551 pack $w.m -side top -fill x -padx 2 -pady 2
2552 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2553 pack $w.ok -side bottom
2554 bind $w <Visibility> "focus $w.ok"
2555 bind $w <Key-Escape> "destroy $w"
2556 bind $w <Key-Return> "destroy $w"
2561 if {[winfo exists $w]} {
2565 if {[tk windowingsystem] eq {aqua}} {
2571 wm title $w [mc "Gitk key bindings"]
2572 message $w.m -text "
2573 [mc "Gitk key bindings:"]
2575 [mc "<%s-Q> Quit" $M1T]
2576 [mc "<Home> Move to first commit"]
2577 [mc "<End> Move to last commit"]
2578 [mc "<Up>, p, i Move up one commit"]
2579 [mc "<Down>, n, k Move down one commit"]
2580 [mc "<Left>, z, j Go back in history list"]
2581 [mc "<Right>, x, l Go forward in history list"]
2582 [mc "<PageUp> Move up one page in commit list"]
2583 [mc "<PageDown> Move down one page in commit list"]
2584 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2585 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2586 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2587 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2588 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2589 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2590 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2591 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2592 [mc "<Delete>, b Scroll diff view up one page"]
2593 [mc "<Backspace> Scroll diff view up one page"]
2594 [mc "<Space> Scroll diff view down one page"]
2595 [mc "u Scroll diff view up 18 lines"]
2596 [mc "d Scroll diff view down 18 lines"]
2597 [mc "<%s-F> Find" $M1T]
2598 [mc "<%s-G> Move to next find hit" $M1T]
2599 [mc "<Return> Move to next find hit"]
2600 [mc "/ Move to next find hit, or redo find"]
2601 [mc "? Move to previous find hit"]
2602 [mc "f Scroll diff view to next file"]
2603 [mc "<%s-S> Search for next hit in diff view" $M1T]
2604 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2605 [mc "<%s-KP+> Increase font size" $M1T]
2606 [mc "<%s-plus> Increase font size" $M1T]
2607 [mc "<%s-KP-> Decrease font size" $M1T]
2608 [mc "<%s-minus> Decrease font size" $M1T]
2611 -justify left -bg white -border 2 -relief groove
2612 pack $w.m -side top -fill both -padx 2 -pady 2
2613 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2614 pack $w.ok -side bottom
2615 bind $w <Visibility> "focus $w.ok"
2616 bind $w <Key-Escape> "destroy $w"
2617 bind $w <Key-Return> "destroy $w"
2620 # Procedures for manipulating the file list window at the
2621 # bottom right of the overall window.
2623 proc treeview {w l openlevs} {
2624 global treecontents treediropen treeheight treeparent treeindex
2634 set treecontents() {}
2635 $w conf -state normal
2637 while {[string range $f 0 $prefixend] ne $prefix} {
2638 if {$lev <= $openlevs} {
2639 $w mark set e:$treeindex($prefix) "end -1c"
2640 $w mark gravity e:$treeindex($prefix) left
2642 set treeheight($prefix) $ht
2643 incr ht [lindex $htstack end]
2644 set htstack [lreplace $htstack end end]
2645 set prefixend [lindex $prefendstack end]
2646 set prefendstack [lreplace $prefendstack end end]
2647 set prefix [string range $prefix 0 $prefixend]
2650 set tail [string range $f [expr {$prefixend+1}] end]
2651 while {[set slash [string first "/" $tail]] >= 0} {
2654 lappend prefendstack $prefixend
2655 incr prefixend [expr {$slash + 1}]
2656 set d [string range $tail 0 $slash]
2657 lappend treecontents($prefix) $d
2658 set oldprefix $prefix
2660 set treecontents($prefix) {}
2661 set treeindex($prefix) [incr ix]
2662 set treeparent($prefix) $oldprefix
2663 set tail [string range $tail [expr {$slash+1}] end]
2664 if {$lev <= $openlevs} {
2666 set treediropen($prefix) [expr {$lev < $openlevs}]
2667 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2668 $w mark set d:$ix "end -1c"
2669 $w mark gravity d:$ix left
2671 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2673 $w image create end -align center -image $bm -padx 1 \
2675 $w insert end $d [highlight_tag $prefix]
2676 $w mark set s:$ix "end -1c"
2677 $w mark gravity s:$ix left
2682 if {$lev <= $openlevs} {
2685 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2687 $w insert end $tail [highlight_tag $f]
2689 lappend treecontents($prefix) $tail
2692 while {$htstack ne {}} {
2693 set treeheight($prefix) $ht
2694 incr ht [lindex $htstack end]
2695 set htstack [lreplace $htstack end end]
2696 set prefixend [lindex $prefendstack end]
2697 set prefendstack [lreplace $prefendstack end end]
2698 set prefix [string range $prefix 0 $prefixend]
2700 $w conf -state disabled
2703 proc linetoelt {l} {
2704 global treeheight treecontents
2709 foreach e $treecontents($prefix) {
2714 if {[string index $e end] eq "/"} {
2715 set n $treeheight($prefix$e)
2727 proc highlight_tree {y prefix} {
2728 global treeheight treecontents cflist
2730 foreach e $treecontents($prefix) {
2732 if {[highlight_tag $path] ne {}} {
2733 $cflist tag add bold $y.0 "$y.0 lineend"
2736 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2737 set y [highlight_tree $y $path]
2743 proc treeclosedir {w dir} {
2744 global treediropen treeheight treeparent treeindex
2746 set ix $treeindex($dir)
2747 $w conf -state normal
2748 $w delete s:$ix e:$ix
2749 set treediropen($dir) 0
2750 $w image configure a:$ix -image tri-rt
2751 $w conf -state disabled
2752 set n [expr {1 - $treeheight($dir)}]
2753 while {$dir ne {}} {
2754 incr treeheight($dir) $n
2755 set dir $treeparent($dir)
2759 proc treeopendir {w dir} {
2760 global treediropen treeheight treeparent treecontents treeindex
2762 set ix $treeindex($dir)
2763 $w conf -state normal
2764 $w image configure a:$ix -image tri-dn
2765 $w mark set e:$ix s:$ix
2766 $w mark gravity e:$ix right
2769 set n [llength $treecontents($dir)]
2770 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2773 incr treeheight($x) $n
2775 foreach e $treecontents($dir) {
2777 if {[string index $e end] eq "/"} {
2778 set iy $treeindex($de)
2779 $w mark set d:$iy e:$ix
2780 $w mark gravity d:$iy left
2781 $w insert e:$ix $str
2782 set treediropen($de) 0
2783 $w image create e:$ix -align center -image tri-rt -padx 1 \
2785 $w insert e:$ix $e [highlight_tag $de]
2786 $w mark set s:$iy e:$ix
2787 $w mark gravity s:$iy left
2788 set treeheight($de) 1
2790 $w insert e:$ix $str
2791 $w insert e:$ix $e [highlight_tag $de]
2794 $w mark gravity e:$ix right
2795 $w conf -state disabled
2796 set treediropen($dir) 1
2797 set top [lindex [split [$w index @0,0] .] 0]
2798 set ht [$w cget -height]
2799 set l [lindex [split [$w index s:$ix] .] 0]
2802 } elseif {$l + $n + 1 > $top + $ht} {
2803 set top [expr {$l + $n + 2 - $ht}]
2811 proc treeclick {w x y} {
2812 global treediropen cmitmode ctext cflist cflist_top
2814 if {$cmitmode ne "tree"} return
2815 if {![info exists cflist_top]} return
2816 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2817 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2818 $cflist tag add highlight $l.0 "$l.0 lineend"
2824 set e [linetoelt $l]
2825 if {[string index $e end] ne "/"} {
2827 } elseif {$treediropen($e)} {
2834 proc setfilelist {id} {
2835 global treefilelist cflist jump_to_here
2837 treeview $cflist $treefilelist($id) 0
2838 if {$jump_to_here ne {}} {
2839 set f [lindex $jump_to_here 0]
2840 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
2846 image create bitmap tri-rt -background black -foreground blue -data {
2847 #define tri-rt_width 13
2848 #define tri-rt_height 13
2849 static unsigned char tri-rt_bits[] = {
2850 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2851 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2854 #define tri-rt-mask_width 13
2855 #define tri-rt-mask_height 13
2856 static unsigned char tri-rt-mask_bits[] = {
2857 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2858 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2861 image create bitmap tri-dn -background black -foreground blue -data {
2862 #define tri-dn_width 13
2863 #define tri-dn_height 13
2864 static unsigned char tri-dn_bits[] = {
2865 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2866 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2869 #define tri-dn-mask_width 13
2870 #define tri-dn-mask_height 13
2871 static unsigned char tri-dn-mask_bits[] = {
2872 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2873 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2877 image create bitmap reficon-T -background black -foreground yellow -data {
2878 #define tagicon_width 13
2879 #define tagicon_height 9
2880 static unsigned char tagicon_bits[] = {
2881 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2882 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2884 #define tagicon-mask_width 13
2885 #define tagicon-mask_height 9
2886 static unsigned char tagicon-mask_bits[] = {
2887 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2888 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2891 #define headicon_width 13
2892 #define headicon_height 9
2893 static unsigned char headicon_bits[] = {
2894 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2895 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2898 #define headicon-mask_width 13
2899 #define headicon-mask_height 9
2900 static unsigned char headicon-mask_bits[] = {
2901 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2902 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2904 image create bitmap reficon-H -background black -foreground green \
2905 -data $rectdata -maskdata $rectmask
2906 image create bitmap reficon-o -background black -foreground "#ddddff" \
2907 -data $rectdata -maskdata $rectmask
2909 proc init_flist {first} {
2910 global cflist cflist_top difffilestart
2912 $cflist conf -state normal
2913 $cflist delete 0.0 end
2915 $cflist insert end $first
2917 $cflist tag add highlight 1.0 "1.0 lineend"
2919 catch {unset cflist_top}
2921 $cflist conf -state disabled
2922 set difffilestart {}
2925 proc highlight_tag {f} {
2926 global highlight_paths
2928 foreach p $highlight_paths {
2929 if {[string match $p $f]} {
2936 proc highlight_filelist {} {
2937 global cmitmode cflist
2939 $cflist conf -state normal
2940 if {$cmitmode ne "tree"} {
2941 set end [lindex [split [$cflist index end] .] 0]
2942 for {set l 2} {$l < $end} {incr l} {
2943 set line [$cflist get $l.0 "$l.0 lineend"]
2944 if {[highlight_tag $line] ne {}} {
2945 $cflist tag add bold $l.0 "$l.0 lineend"
2951 $cflist conf -state disabled
2954 proc unhighlight_filelist {} {
2957 $cflist conf -state normal
2958 $cflist tag remove bold 1.0 end
2959 $cflist conf -state disabled
2962 proc add_flist {fl} {
2965 $cflist conf -state normal
2967 $cflist insert end "\n"
2968 $cflist insert end $f [highlight_tag $f]
2970 $cflist conf -state disabled
2973 proc sel_flist {w x y} {
2974 global ctext difffilestart cflist cflist_top cmitmode
2976 if {$cmitmode eq "tree"} return
2977 if {![info exists cflist_top]} return
2978 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2979 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2980 $cflist tag add highlight $l.0 "$l.0 lineend"
2985 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2989 proc pop_flist_menu {w X Y x y} {
2990 global ctext cflist cmitmode flist_menu flist_menu_file
2991 global treediffs diffids
2994 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2996 if {$cmitmode eq "tree"} {
2997 set e [linetoelt $l]
2998 if {[string index $e end] eq "/"} return
3000 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3002 set flist_menu_file $e
3003 set xdiffstate "normal"
3004 if {$cmitmode eq "tree"} {
3005 set xdiffstate "disabled"
3007 # Disable "External diff" item in tree mode
3008 $flist_menu entryconf 2 -state $xdiffstate
3009 tk_popup $flist_menu $X $Y
3012 proc find_ctext_fileinfo {line} {
3013 global ctext_file_names ctext_file_lines
3015 set ok [bsearch $ctext_file_lines $line]
3016 set tline [lindex $ctext_file_lines $ok]
3018 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3021 return [list [lindex $ctext_file_names $ok] $tline]
3025 proc pop_diff_menu {w X Y x y} {
3026 global ctext diff_menu flist_menu_file
3027 global diff_menu_txtpos diff_menu_line
3028 global diff_menu_filebase
3030 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3031 set diff_menu_line [lindex $diff_menu_txtpos 0]
3032 # don't pop up the menu on hunk-separator or file-separator lines
3033 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3037 set f [find_ctext_fileinfo $diff_menu_line]
3038 if {$f eq {}} return
3039 set flist_menu_file [lindex $f 0]
3040 set diff_menu_filebase [lindex $f 1]
3041 tk_popup $diff_menu $X $Y
3044 proc flist_hl {only} {
3045 global flist_menu_file findstring gdttype
3047 set x [shellquote $flist_menu_file]
3048 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3051 append findstring " " $x
3053 set gdttype [mc "touching paths:"]
3056 proc save_file_from_commit {filename output what} {
3059 if {[catch {exec git show $filename -- > $output} err]} {
3060 if {[string match "fatal: bad revision *" $err]} {
3063 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3069 proc external_diff_get_one_file {diffid filename diffdir} {
3070 global nullid nullid2 nullfile
3073 if {$diffid == $nullid} {
3074 set difffile [file join [file dirname $gitdir] $filename]
3075 if {[file exists $difffile]} {
3080 if {$diffid == $nullid2} {
3081 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3082 return [save_file_from_commit :$filename $difffile index]
3084 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3085 return [save_file_from_commit $diffid:$filename $difffile \
3089 proc external_diff {} {
3090 global gitktmpdir nullid nullid2
3091 global flist_menu_file
3094 global gitdir extdifftool
3096 if {[llength $diffids] == 1} {
3097 # no reference commit given
3098 set diffidto [lindex $diffids 0]
3099 if {$diffidto eq $nullid} {
3100 # diffing working copy with index
3101 set diffidfrom $nullid2
3102 } elseif {$diffidto eq $nullid2} {
3103 # diffing index with HEAD
3104 set diffidfrom "HEAD"
3106 # use first parent commit
3107 global parentlist selectedline
3108 set diffidfrom [lindex $parentlist $selectedline 0]
3111 set diffidfrom [lindex $diffids 0]
3112 set diffidto [lindex $diffids 1]
3115 # make sure that several diffs wont collide
3116 if {![info exists gitktmpdir]} {
3117 set gitktmpdir [file join [file dirname $gitdir] \
3118 [format ".gitk-tmp.%s" [pid]]]
3119 if {[catch {file mkdir $gitktmpdir} err]} {
3120 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3127 set diffdir [file join $gitktmpdir $diffnum]
3128 if {[catch {file mkdir $diffdir} err]} {
3129 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3133 # gather files to diff
3134 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3135 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3137 if {$difffromfile ne {} && $difftofile ne {}} {
3138 set cmd [concat | [shellsplit $extdifftool] \
3139 [list $difffromfile $difftofile]]
3140 if {[catch {set fl [open $cmd r]} err]} {
3141 file delete -force $diffdir
3142 error_popup "$extdifftool: [mc "command failed:"] $err"
3144 fconfigure $fl -blocking 0
3145 filerun $fl [list delete_at_eof $fl $diffdir]
3150 proc find_hunk_blamespec {base line} {
3153 # Find and parse the hunk header
3154 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3155 if {$s_lix eq {}} return
3157 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3158 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3159 s_line old_specs osz osz1 new_line nsz]} {
3163 # base lines for the parents
3164 set base_lines [list $new_line]
3165 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3166 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3167 old_spec old_line osz]} {
3170 lappend base_lines $old_line
3173 # Now scan the lines to determine offset within the hunk
3174 set max_parent [expr {[llength $base_lines]-2}]
3176 set s_lno [lindex [split $s_lix "."] 0]
3178 # Determine if the line is removed
3179 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3180 if {[string match {[-+ ]*} $chunk]} {
3181 set removed_idx [string first "-" $chunk]
3182 # Choose a parent index
3183 if {$removed_idx >= 0} {
3184 set parent $removed_idx
3186 set unchanged_idx [string first " " $chunk]
3187 if {$unchanged_idx >= 0} {
3188 set parent $unchanged_idx
3190 # blame the current commit
3194 # then count other lines that belong to it
3195 for {set i $line} {[incr i -1] > $s_lno} {} {
3196 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3197 # Determine if the line is removed
3198 set removed_idx [string first "-" $chunk]
3200 set code [string index $chunk $parent]
3201 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3205 if {$removed_idx < 0} {
3215 incr dline [lindex $base_lines $parent]
3216 return [list $parent $dline]
3219 proc external_blame_diff {} {
3220 global currentid diffmergeid cmitmode
3221 global diff_menu_txtpos diff_menu_line
3222 global diff_menu_filebase flist_menu_file
3224 if {$cmitmode eq "tree"} {
3226 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3228 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3230 set parent_idx [lindex $hinfo 0]
3231 set line [lindex $hinfo 1]
3238 external_blame $parent_idx $line
3241 proc external_blame {parent_idx {line {}}} {
3242 global flist_menu_file
3243 global nullid nullid2
3244 global parentlist selectedline currentid
3246 if {$parent_idx > 0} {
3247 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3249 set base_commit $currentid
3252 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3253 error_popup [mc "No such commit"]
3257 set cmdline [list git gui blame]
3258 if {$line ne {} && $line > 1} {
3259 lappend cmdline "--line=$line"
3261 lappend cmdline $base_commit $flist_menu_file
3262 if {[catch {eval exec $cmdline &} err]} {
3263 error_popup "[mc "git gui blame: command failed:"] $err"
3267 proc show_line_source {} {
3268 global cmitmode currentid parents curview blamestuff blameinst
3269 global diff_menu_line diff_menu_filebase flist_menu_file
3271 if {$cmitmode eq "tree"} {
3273 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3275 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3276 if {$h eq {}} return
3277 set pi [lindex $h 0]
3279 mark_ctext_line $diff_menu_line
3282 set id [lindex $parents($curview,$currentid) [expr {$pi - 1}]]
3283 set line [lindex $h 1]
3286 set f [open [list | git blame -p -L$line,+1 $id -- $flist_menu_file] r]
3288 error_popup [mc "Couldn't start git blame: %s" $err]
3291 fconfigure $f -blocking 0
3292 set i [reg_instance $f]
3293 set blamestuff($i) {}
3295 filerun $f [list read_line_source $f $i]
3298 proc stopblaming {} {
3301 if {[info exists blameinst]} {
3302 stop_instance $blameinst
3307 proc read_line_source {fd inst} {
3308 global blamestuff curview commfd blameinst
3310 while {[gets $fd line] >= 0} {
3311 lappend blamestuff($inst) $line
3318 fconfigure $fd -blocking 1
3319 if {[catch {close $fd} err]} {
3320 error_popup [mc "Error running git blame: %s" $err]
3325 set line [split [lindex $blamestuff($inst) 0] " "]
3326 set id [lindex $line 0]
3327 set lnum [lindex $line 1]
3328 if {[string length $id] == 40 && [string is xdigit $id] &&
3329 [string is digit -strict $lnum]} {
3330 # look for "filename" line
3331 foreach l $blamestuff($inst) {
3332 if {[string match "filename *" $l]} {
3333 set fname [string range $l 9 end]
3339 # all looks good, select it
3340 if {[commitinview $id $curview]} {
3341 selectline [rowofcommit $id] 1 [list $fname $lnum]
3343 error_popup [mc "That line comes from commit %s, \
3344 which is not in this view" [shortids $id]]
3347 puts "oops couldn't parse git blame output"
3352 # delete $dir when we see eof on $f (presumably because the child has exited)
3353 proc delete_at_eof {f dir} {
3354 while {[gets $f line] >= 0} {}
3356 if {[catch {close $f} err]} {
3357 error_popup "[mc "External diff viewer failed:"] $err"
3359 file delete -force $dir
3365 # Functions for adding and removing shell-type quoting
3367 proc shellquote {str} {
3368 if {![string match "*\['\"\\ \t]*" $str]} {
3371 if {![string match "*\['\"\\]*" $str]} {
3374 if {![string match "*'*" $str]} {
3377 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3380 proc shellarglist {l} {
3386 append str [shellquote $a]
3391 proc shelldequote {str} {
3396 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3397 append ret [string range $str $used end]
3398 set used [string length $str]
3401 set first [lindex $first 0]
3402 set ch [string index $str $first]
3403 if {$first > $used} {
3404 append ret [string range $str $used [expr {$first - 1}]]
3407 if {$ch eq " " || $ch eq "\t"} break
3410 set first [string first "'" $str $used]
3412 error "unmatched single-quote"
3414 append ret [string range $str $used [expr {$first - 1}]]
3419 if {$used >= [string length $str]} {
3420 error "trailing backslash"
3422 append ret [string index $str $used]
3427 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3428 error "unmatched double-quote"
3430 set first [lindex $first 0]
3431 set ch [string index $str $first]
3432 if {$first > $used} {
3433 append ret [string range $str $used [expr {$first - 1}]]
3436 if {$ch eq "\""} break
3438 append ret [string index $str $used]
3442 return [list $used $ret]
3445 proc shellsplit {str} {
3448 set str [string trimleft $str]
3449 if {$str eq {}} break
3450 set dq [shelldequote $str]
3451 set n [lindex $dq 0]
3452 set word [lindex $dq 1]
3453 set str [string range $str $n end]
3459 # Code to implement multiple views
3461 proc newview {ishighlight} {
3462 global nextviewnum newviewname newviewperm newishighlight
3463 global newviewargs revtreeargs viewargscmd newviewargscmd curview
3465 set newishighlight $ishighlight
3467 if {[winfo exists $top]} {
3471 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3472 set newviewperm($nextviewnum) 0
3473 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
3474 set newviewargscmd($nextviewnum) $viewargscmd($curview)
3475 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3480 global viewname viewperm newviewname newviewperm
3481 global viewargs newviewargs viewargscmd newviewargscmd
3483 set top .gitkvedit-$curview
3484 if {[winfo exists $top]} {
3488 set newviewname($curview) $viewname($curview)
3489 set newviewperm($curview) $viewperm($curview)
3490 set newviewargs($curview) [shellarglist $viewargs($curview)]
3491 set newviewargscmd($curview) $viewargscmd($curview)
3492 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3495 proc vieweditor {top n title} {
3496 global newviewname newviewperm viewfiles bgcolor
3499 wm title $top $title
3500 label $top.nl -text [mc "Name"]
3501 entry $top.name -width 20 -textvariable newviewname($n)
3502 grid $top.nl $top.name -sticky w -pady 5
3503 checkbutton $top.perm -text [mc "Remember this view"] \
3504 -variable newviewperm($n)
3505 grid $top.perm - -pady 5 -sticky w
3506 message $top.al -aspect 1000 \
3507 -text [mc "Commits to include (arguments to git log):"]
3508 grid $top.al - -sticky w -pady 5
3509 entry $top.args -width 50 -textvariable newviewargs($n) \
3510 -background $bgcolor
3511 grid $top.args - -sticky ew -padx 5
3513 message $top.ac -aspect 1000 \
3514 -text [mc "Command to generate more commits to include:"]
3515 grid $top.ac - -sticky w -pady 5
3516 entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
3518 grid $top.argscmd - -sticky ew -padx 5
3520 message $top.l -aspect 1000 \
3521 -text [mc "Enter files and directories to include, one per line:"]
3522 grid $top.l - -sticky w
3523 text $top.t -width 40 -height 10 -background $bgcolor -font uifont
3524 if {[info exists viewfiles($n)]} {
3525 foreach f $viewfiles($n) {
3526 $top.t insert end $f
3527 $top.t insert end "\n"
3529 $top.t delete {end - 1c} end
3530 $top.t mark set insert 0.0
3532 grid $top.t - -sticky ew -padx 5
3534 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3535 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3536 grid $top.buts.ok $top.buts.can
3537 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3538 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3539 grid $top.buts - -pady 10 -sticky ew
3543 proc doviewmenu {m first cmd op argv} {
3544 set nmenu [$m index end]
3545 for {set i $first} {$i <= $nmenu} {incr i} {
3546 if {[$m entrycget $i -command] eq $cmd} {
3547 eval $m $op $i $argv
3553 proc allviewmenus {n op args} {
3556 doviewmenu .bar.view 5 [list showview $n] $op $args
3557 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3560 proc newviewok {top n} {
3561 global nextviewnum newviewperm newviewname newishighlight
3562 global viewname viewfiles viewperm selectedview curview
3563 global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
3566 set newargs [shellsplit $newviewargs($n)]
3568 error_popup "[mc "Error in commit selection arguments:"] $err"
3574 foreach f [split [$top.t get 0.0 end] "\n"] {
3575 set ft [string trim $f]
3580 if {![info exists viewfiles($n)]} {
3581 # creating a new view
3583 set viewname($n) $newviewname($n)
3584 set viewperm($n) $newviewperm($n)
3585 set viewfiles($n) $files
3586 set viewargs($n) $newargs
3587 set viewargscmd($n) $newviewargscmd($n)
3589 if {!$newishighlight} {
3592 run addvhighlight $n
3595 # editing an existing view
3596 set viewperm($n) $newviewperm($n)
3597 if {$newviewname($n) ne $viewname($n)} {
3598 set viewname($n) $newviewname($n)
3599 doviewmenu .bar.view 5 [list showview $n] \
3600 entryconf [list -label $viewname($n)]
3601 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3602 # entryconf [list -label $viewname($n) -value $viewname($n)]
3604 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3605 $newviewargscmd($n) ne $viewargscmd($n)} {
3606 set viewfiles($n) $files
3607 set viewargs($n) $newargs
3608 set viewargscmd($n) $newviewargscmd($n)
3609 if {$curview == $n} {
3614 catch {destroy $top}
3618 global curview viewperm hlview selectedhlview
3620 if {$curview == 0} return
3621 if {[info exists hlview] && $hlview == $curview} {
3622 set selectedhlview [mc "None"]
3625 allviewmenus $curview delete
3626 set viewperm($curview) 0
3630 proc addviewmenu {n} {
3631 global viewname viewhlmenu
3633 .bar.view add radiobutton -label $viewname($n) \
3634 -command [list showview $n] -variable selectedview -value $n
3635 #$viewhlmenu add radiobutton -label $viewname($n) \
3636 # -command [list addvhighlight $n] -variable selectedhlview
3640 global curview cached_commitrow ordertok
3641 global displayorder parentlist rowidlist rowisopt rowfinal
3642 global colormap rowtextx nextcolor canvxmax
3643 global numcommits viewcomplete
3644 global selectedline currentid canv canvy0
3646 global pending_select mainheadid
3649 global hlview selectedhlview commitinterest
3651 if {$n == $curview} return
3653 set ymax [lindex [$canv cget -scrollregion] 3]
3654 set span [$canv yview]
3655 set ytop [expr {[lindex $span 0] * $ymax}]
3656 set ybot [expr {[lindex $span 1] * $ymax}]
3657 set yscreen [expr {($ybot - $ytop) / 2}]
3658 if {$selectedline ne {}} {
3659 set selid $currentid
3660 set y [yc $selectedline]
3661 if {$ytop < $y && $y < $ybot} {
3662 set yscreen [expr {$y - $ytop}]
3664 } elseif {[info exists pending_select]} {
3665 set selid $pending_select
3666 unset pending_select
3670 catch {unset treediffs}
3672 if {[info exists hlview] && $hlview == $n} {
3674 set selectedhlview [mc "None"]
3676 catch {unset commitinterest}
3677 catch {unset cached_commitrow}
3678 catch {unset ordertok}
3682 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3683 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3686 if {![info exists viewcomplete($n)]} {
3696 set numcommits $commitidx($n)
3698 catch {unset colormap}
3699 catch {unset rowtextx}
3701 set canvxmax [$canv cget -width]
3707 if {$selid ne {} && [commitinview $selid $n]} {
3708 set row [rowofcommit $selid]
3709 # try to get the selected row in the same position on the screen
3710 set ymax [lindex [$canv cget -scrollregion] 3]
3711 set ytop [expr {[yc $row] - $yscreen}]
3715 set yf [expr {$ytop * 1.0 / $ymax}]
3717 allcanvs yview moveto $yf
3721 } elseif {!$viewcomplete($n)} {
3722 reset_pending_select $selid
3724 reset_pending_select {}
3726 if {[commitinview $pending_select $curview]} {
3727 selectline [rowofcommit $pending_select] 1
3729 set row [first_real_row]
3730 if {$row < $numcommits} {
3735 if {!$viewcomplete($n)} {
3736 if {$numcommits == 0} {
3737 show_status [mc "Reading commits..."]
3739 } elseif {$numcommits == 0} {
3740 show_status [mc "No commits selected"]
3744 # Stuff relating to the highlighting facility
3746 proc ishighlighted {id} {
3747 global vhighlights fhighlights nhighlights rhighlights
3749 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3750 return $nhighlights($id)
3752 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3753 return $vhighlights($id)
3755 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3756 return $fhighlights($id)
3758 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3759 return $rhighlights($id)
3764 proc bolden {row font} {
3765 global canv linehtag selectedline boldrows
3767 lappend boldrows $row
3768 $canv itemconf $linehtag($row) -font $font
3769 if {$row == $selectedline} {
3771 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3772 -outline {{}} -tags secsel \
3773 -fill [$canv cget -selectbackground]]
3778 proc bolden_name {row font} {
3779 global canv2 linentag selectedline boldnamerows
3781 lappend boldnamerows $row
3782 $canv2 itemconf $linentag($row) -font $font
3783 if {$row == $selectedline} {
3784 $canv2 delete secsel
3785 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3786 -outline {{}} -tags secsel \
3787 -fill [$canv2 cget -selectbackground]]
3796 foreach row $boldrows {
3797 if {![ishighlighted [commitonrow $row]]} {
3798 bolden $row mainfont
3800 lappend stillbold $row
3803 set boldrows $stillbold
3806 proc addvhighlight {n} {
3807 global hlview viewcomplete curview vhl_done commitidx
3809 if {[info exists hlview]} {
3813 if {$n != $curview && ![info exists viewcomplete($n)]} {
3816 set vhl_done $commitidx($hlview)
3817 if {$vhl_done > 0} {
3822 proc delvhighlight {} {
3823 global hlview vhighlights
3825 if {![info exists hlview]} return
3827 catch {unset vhighlights}
3831 proc vhighlightmore {} {
3832 global hlview vhl_done commitidx vhighlights curview
3834 set max $commitidx($hlview)
3835 set vr [visiblerows]
3836 set r0 [lindex $vr 0]
3837 set r1 [lindex $vr 1]
3838 for {set i $vhl_done} {$i < $max} {incr i} {
3839 set id [commitonrow $i $hlview]
3840 if {[commitinview $id $curview]} {
3841 set row [rowofcommit $id]
3842 if {$r0 <= $row && $row <= $r1} {
3843 if {![highlighted $row]} {
3844 bolden $row mainfontbold
3846 set vhighlights($id) 1
3854 proc askvhighlight {row id} {
3855 global hlview vhighlights iddrawn
3857 if {[commitinview $id $hlview]} {
3858 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
3859 bolden $row mainfontbold
3861 set vhighlights($id) 1
3863 set vhighlights($id) 0
3867 proc hfiles_change {} {
3868 global highlight_files filehighlight fhighlights fh_serial
3869 global highlight_paths gdttype
3871 if {[info exists filehighlight]} {
3872 # delete previous highlights
3873 catch {close $filehighlight}
3875 catch {unset fhighlights}
3877 unhighlight_filelist
3879 set highlight_paths {}
3880 after cancel do_file_hl $fh_serial
3882 if {$highlight_files ne {}} {
3883 after 300 do_file_hl $fh_serial
3887 proc gdttype_change {name ix op} {
3888 global gdttype highlight_files findstring findpattern
3891 if {$findstring ne {}} {
3892 if {$gdttype eq [mc "containing:"]} {
3893 if {$highlight_files ne {}} {
3894 set highlight_files {}
3899 if {$findpattern ne {}} {
3903 set highlight_files $findstring
3908 # enable/disable findtype/findloc menus too
3911 proc find_change {name ix op} {
3912 global gdttype findstring highlight_files
3915 if {$gdttype eq [mc "containing:"]} {
3918 if {$highlight_files ne $findstring} {
3919 set highlight_files $findstring
3926 proc findcom_change args {
3927 global nhighlights boldnamerows
3928 global findpattern findtype findstring gdttype
3931 # delete previous highlights, if any
3932 foreach row $boldnamerows {
3933 bolden_name $row mainfont
3936 catch {unset nhighlights}
3939 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
3941 } elseif {$findtype eq [mc "Regexp"]} {
3942 set findpattern $findstring
3944 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3946 set findpattern "*$e*"
3950 proc makepatterns {l} {
3953 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3954 if {[string index $ee end] eq "/"} {
3964 proc do_file_hl {serial} {
3965 global highlight_files filehighlight highlight_paths gdttype fhl_list
3967 if {$gdttype eq [mc "touching paths:"]} {
3968 if {[catch {set paths [shellsplit $highlight_files]}]} return
3969 set highlight_paths [makepatterns $paths]
3971 set gdtargs [concat -- $paths]
3972 } elseif {$gdttype eq [mc "adding/removing string:"]} {
3973 set gdtargs [list "-S$highlight_files"]
3975 # must be "containing:", i.e. we're searching commit info
3978 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3979 set filehighlight [open $cmd r+]
3980 fconfigure $filehighlight -blocking 0
3981 filerun $filehighlight readfhighlight
3987 proc flushhighlights {} {
3988 global filehighlight fhl_list
3990 if {[info exists filehighlight]} {
3992 puts $filehighlight ""
3993 flush $filehighlight
3997 proc askfilehighlight {row id} {
3998 global filehighlight fhighlights fhl_list
4000 lappend fhl_list $id
4001 set fhighlights($id) -1
4002 puts $filehighlight $id
4005 proc readfhighlight {} {
4006 global filehighlight fhighlights curview iddrawn
4007 global fhl_list find_dirn
4009 if {![info exists filehighlight]} {
4013 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4014 set line [string trim $line]
4015 set i [lsearch -exact $fhl_list $line]
4016 if {$i < 0} continue
4017 for {set j 0} {$j < $i} {incr j} {
4018 set id [lindex $fhl_list $j]
4019 set fhighlights($id) 0
4021 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4022 if {$line eq {}} continue
4023 if {![commitinview $line $curview]} continue
4024 set row [rowofcommit $line]
4025 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4026 bolden $row mainfontbold
4028 set fhighlights($line) 1
4030 if {[eof $filehighlight]} {
4032 puts "oops, git diff-tree died"
4033 catch {close $filehighlight}
4037 if {[info exists find_dirn]} {
4043 proc doesmatch {f} {
4044 global findtype findpattern
4046 if {$findtype eq [mc "Regexp"]} {
4047 return [regexp $findpattern $f]
4048 } elseif {$findtype eq [mc "IgnCase"]} {
4049 return [string match -nocase $findpattern $f]
4051 return [string match $findpattern $f]
4055 proc askfindhighlight {row id} {
4056 global nhighlights commitinfo iddrawn
4058 global markingmatches
4060 if {![info exists commitinfo($id)]} {
4063 set info $commitinfo($id)
4065 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4066 foreach f $info ty $fldtypes {
4067 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4069 if {$ty eq [mc "Author"]} {
4076 if {$isbold && [info exists iddrawn($id)]} {
4077 if {![ishighlighted $id]} {
4078 bolden $row mainfontbold
4080 bolden_name $row mainfontbold
4083 if {$markingmatches} {
4084 markrowmatches $row $id
4087 set nhighlights($id) $isbold
4090 proc markrowmatches {row id} {
4091 global canv canv2 linehtag linentag commitinfo findloc
4093 set headline [lindex $commitinfo($id) 0]
4094 set author [lindex $commitinfo($id) 1]
4095 $canv delete match$row
4096 $canv2 delete match$row
4097 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4098 set m [findmatches $headline]
4100 markmatches $canv $row $headline $linehtag($row) $m \
4101 [$canv itemcget $linehtag($row) -font] $row
4104 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4105 set m [findmatches $author]
4107 markmatches $canv2 $row $author $linentag($row) $m \
4108 [$canv2 itemcget $linentag($row) -font] $row
4113 proc vrel_change {name ix op} {
4114 global highlight_related
4117 if {$highlight_related ne [mc "None"]} {
4122 # prepare for testing whether commits are descendents or ancestors of a
4123 proc rhighlight_sel {a} {
4124 global descendent desc_todo ancestor anc_todo
4125 global highlight_related
4127 catch {unset descendent}
4128 set desc_todo [list $a]
4129 catch {unset ancestor}
4130 set anc_todo [list $a]
4131 if {$highlight_related ne [mc "None"]} {
4137 proc rhighlight_none {} {
4140 catch {unset rhighlights}
4144 proc is_descendent {a} {
4145 global curview children descendent desc_todo
4148 set la [rowofcommit $a]
4152 for {set i 0} {$i < [llength $todo]} {incr i} {
4153 set do [lindex $todo $i]
4154 if {[rowofcommit $do] < $la} {
4155 lappend leftover $do
4158 foreach nk $children($v,$do) {
4159 if {![info exists descendent($nk)]} {
4160 set descendent($nk) 1
4168 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4172 set descendent($a) 0
4173 set desc_todo $leftover
4176 proc is_ancestor {a} {
4177 global curview parents ancestor anc_todo
4180 set la [rowofcommit $a]
4184 for {set i 0} {$i < [llength $todo]} {incr i} {
4185 set do [lindex $todo $i]
4186 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4187 lappend leftover $do
4190 foreach np $parents($v,$do) {
4191 if {![info exists ancestor($np)]} {
4200 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4205 set anc_todo $leftover
4208 proc askrelhighlight {row id} {
4209 global descendent highlight_related iddrawn rhighlights
4210 global selectedline ancestor
4212 if {$selectedline eq {}} return
4214 if {$highlight_related eq [mc "Descendant"] ||
4215 $highlight_related eq [mc "Not descendant"]} {
4216 if {![info exists descendent($id)]} {
4219 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4222 } elseif {$highlight_related eq [mc "Ancestor"] ||
4223 $highlight_related eq [mc "Not ancestor"]} {
4224 if {![info exists ancestor($id)]} {
4227 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4231 if {[info exists iddrawn($id)]} {
4232 if {$isbold && ![ishighlighted $id]} {
4233 bolden $row mainfontbold
4236 set rhighlights($id) $isbold
4239 # Graph layout functions
4241 proc shortids {ids} {
4244 if {[llength $id] > 1} {
4245 lappend res [shortids $id]
4246 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4247 lappend res [string range $id 0 7]
4258 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4259 if {($n & $mask) != 0} {
4260 set ret [concat $ret $o]
4262 set o [concat $o $o]
4267 proc ordertoken {id} {
4268 global ordertok curview varcid varcstart varctok curview parents children
4269 global nullid nullid2
4271 if {[info exists ordertok($id)]} {
4272 return $ordertok($id)
4277 if {[info exists varcid($curview,$id)]} {
4278 set a $varcid($curview,$id)
4279 set p [lindex $varcstart($curview) $a]
4281 set p [lindex $children($curview,$id) 0]
4283 if {[info exists ordertok($p)]} {
4284 set tok $ordertok($p)
4287 set id [first_real_child $curview,$p]
4290 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4293 if {[llength $parents($curview,$id)] == 1} {
4294 lappend todo [list $p {}]
4296 set j [lsearch -exact $parents($curview,$id) $p]
4298 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4300 lappend todo [list $p [strrep $j]]
4303 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4304 set p [lindex $todo $i 0]
4305 append tok [lindex $todo $i 1]
4306 set ordertok($p) $tok
4308 set ordertok($origid) $tok
4312 # Work out where id should go in idlist so that order-token
4313 # values increase from left to right
4314 proc idcol {idlist id {i 0}} {
4315 set t [ordertoken $id]
4319 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4320 if {$i > [llength $idlist]} {
4321 set i [llength $idlist]
4323 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4326 if {$t > [ordertoken [lindex $idlist $i]]} {
4327 while {[incr i] < [llength $idlist] &&
4328 $t >= [ordertoken [lindex $idlist $i]]} {}
4334 proc initlayout {} {
4335 global rowidlist rowisopt rowfinal displayorder parentlist
4336 global numcommits canvxmax canv
4338 global colormap rowtextx
4347 set canvxmax [$canv cget -width]
4348 catch {unset colormap}
4349 catch {unset rowtextx}
4353 proc setcanvscroll {} {
4354 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4355 global lastscrollset lastscrollrows
4357 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4358 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4359 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4360 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4361 set lastscrollset [clock clicks -milliseconds]
4362 set lastscrollrows $numcommits
4365 proc visiblerows {} {
4366 global canv numcommits linespc
4368 set ymax [lindex [$canv cget -scrollregion] 3]
4369 if {$ymax eq {} || $ymax == 0} return
4371 set y0 [expr {int([lindex $f 0] * $ymax)}]
4372 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4376 set y1 [expr {int([lindex $f 1] * $ymax)}]
4377 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4378 if {$r1 >= $numcommits} {
4379 set r1 [expr {$numcommits - 1}]
4381 return [list $r0 $r1]
4384 proc layoutmore {} {
4385 global commitidx viewcomplete curview
4386 global numcommits pending_select curview
4387 global lastscrollset lastscrollrows
4389 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4390 [clock clicks -milliseconds] - $lastscrollset > 500} {
4393 if {[info exists pending_select] &&
4394 [commitinview $pending_select $curview]} {
4396 selectline [rowofcommit $pending_select] 1
4401 proc doshowlocalchanges {} {
4402 global curview mainheadid
4404 if {$mainheadid eq {}} return
4405 if {[commitinview $mainheadid $curview]} {
4408 interestedin $mainheadid dodiffindex
4412 proc dohidelocalchanges {} {
4413 global nullid nullid2 lserial curview
4415 if {[commitinview $nullid $curview]} {
4416 removefakerow $nullid
4418 if {[commitinview $nullid2 $curview]} {
4419 removefakerow $nullid2
4424 # spawn off a process to do git diff-index --cached HEAD
4425 proc dodiffindex {} {
4426 global lserial showlocalchanges
4429 if {!$showlocalchanges || !$isworktree} return
4431 set fd [open "|git diff-index --cached HEAD" r]
4432 fconfigure $fd -blocking 0
4433 set i [reg_instance $fd]
4434 filerun $fd [list readdiffindex $fd $lserial $i]
4437 proc readdiffindex {fd serial inst} {
4438 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4441 if {[gets $fd line] < 0} {
4447 # we only need to see one line and we don't really care what it says...
4450 if {$serial != $lserial} {
4454 # now see if there are any local changes not checked in to the index
4455 set fd [open "|git diff-files" r]
4456 fconfigure $fd -blocking 0
4457 set i [reg_instance $fd]
4458 filerun $fd [list readdifffiles $fd $serial $i]
4460 if {$isdiff && ![commitinview $nullid2 $curview]} {
4461 # add the line for the changes in the index to the graph
4462 set hl [mc "Local changes checked in to index but not committed"]
4463 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4464 set commitdata($nullid2) "\n $hl\n"
4465 if {[commitinview $nullid $curview]} {
4466 removefakerow $nullid
4468 insertfakerow $nullid2 $mainheadid
4469 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4470 removefakerow $nullid2
4475 proc readdifffiles {fd serial inst} {
4476 global mainheadid nullid nullid2 curview
4477 global commitinfo commitdata lserial
4480 if {[gets $fd line] < 0} {
4486 # we only need to see one line and we don't really care what it says...
4489 if {$serial != $lserial} {
4493 if {$isdiff && ![commitinview $nullid $curview]} {
4494 # add the line for the local diff to the graph
4495 set hl [mc "Local uncommitted changes, not checked in to index"]
4496 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4497 set commitdata($nullid) "\n $hl\n"
4498 if {[commitinview $nullid2 $curview]} {
4503 insertfakerow $nullid $p
4504 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4505 removefakerow $nullid
4510 proc nextuse {id row} {
4511 global curview children
4513 if {[info exists children($curview,$id)]} {
4514 foreach kid $children($curview,$id) {
4515 if {![commitinview $kid $curview]} {
4518 if {[rowofcommit $kid] > $row} {
4519 return [rowofcommit $kid]
4523 if {[commitinview $id $curview]} {
4524 return [rowofcommit $id]
4529 proc prevuse {id row} {
4530 global curview children
4533 if {[info exists children($curview,$id)]} {
4534 foreach kid $children($curview,$id) {
4535 if {![commitinview $kid $curview]} break
4536 if {[rowofcommit $kid] < $row} {
4537 set ret [rowofcommit $kid]
4544 proc make_idlist {row} {
4545 global displayorder parentlist uparrowlen downarrowlen mingaplen
4546 global commitidx curview children
4548 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4552 set ra [expr {$row - $downarrowlen}]
4556 set rb [expr {$row + $uparrowlen}]
4557 if {$rb > $commitidx($curview)} {
4558 set rb $commitidx($curview)
4560 make_disporder $r [expr {$rb + 1}]
4562 for {} {$r < $ra} {incr r} {
4563 set nextid [lindex $displayorder [expr {$r + 1}]]
4564 foreach p [lindex $parentlist $r] {
4565 if {$p eq $nextid} continue
4566 set rn [nextuse $p $r]
4568 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4569 lappend ids [list [ordertoken $p] $p]
4573 for {} {$r < $row} {incr r} {
4574 set nextid [lindex $displayorder [expr {$r + 1}]]
4575 foreach p [lindex $parentlist $r] {
4576 if {$p eq $nextid} continue
4577 set rn [nextuse $p $r]
4578 if {$rn < 0 || $rn >= $row} {
4579 lappend ids [list [ordertoken $p] $p]
4583 set id [lindex $displayorder $row]
4584 lappend ids [list [ordertoken $id] $id]
4586 foreach p [lindex $parentlist $r] {
4587 set firstkid [lindex $children($curview,$p) 0]
4588 if {[rowofcommit $firstkid] < $row} {
4589 lappend ids [list [ordertoken $p] $p]
4593 set id [lindex $displayorder $r]
4595 set firstkid [lindex $children($curview,$id) 0]
4596 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4597 lappend ids [list [ordertoken $id] $id]
4602 foreach idx [lsort -unique $ids] {
4603 lappend idlist [lindex $idx 1]
4608 proc rowsequal {a b} {
4609 while {[set i [lsearch -exact $a {}]] >= 0} {
4610 set a [lreplace $a $i $i]
4612 while {[set i [lsearch -exact $b {}]] >= 0} {
4613 set b [lreplace $b $i $i]
4615 return [expr {$a eq $b}]
4618 proc makeupline {id row rend col} {
4619 global rowidlist uparrowlen downarrowlen mingaplen
4621 for {set r $rend} {1} {set r $rstart} {
4622 set rstart [prevuse $id $r]
4623 if {$rstart < 0} return
4624 if {$rstart < $row} break
4626 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4627 set rstart [expr {$rend - $uparrowlen - 1}]
4629 for {set r $rstart} {[incr r] <= $row} {} {
4630 set idlist [lindex $rowidlist $r]
4631 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4632 set col [idcol $idlist $id $col]
4633 lset rowidlist $r [linsert $idlist $col $id]
4639 proc layoutrows {row endrow} {
4640 global rowidlist rowisopt rowfinal displayorder
4641 global uparrowlen downarrowlen maxwidth mingaplen
4642 global children parentlist
4643 global commitidx viewcomplete curview
4645 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4648 set rm1 [expr {$row - 1}]
4649 foreach id [lindex $rowidlist $rm1] {
4654 set final [lindex $rowfinal $rm1]
4656 for {} {$row < $endrow} {incr row} {
4657 set rm1 [expr {$row - 1}]
4658 if {$rm1 < 0 || $idlist eq {}} {
4659 set idlist [make_idlist $row]
4662 set id [lindex $displayorder $rm1]
4663 set col [lsearch -exact $idlist $id]
4664 set idlist [lreplace $idlist $col $col]
4665 foreach p [lindex $parentlist $rm1] {
4666 if {[lsearch -exact $idlist $p] < 0} {
4667 set col [idcol $idlist $p $col]
4668 set idlist [linsert $idlist $col $p]
4669 # if not the first child, we have to insert a line going up
4670 if {$id ne [lindex $children($curview,$p) 0]} {
4671 makeupline $p $rm1 $row $col
4675 set id [lindex $displayorder $row]
4676 if {$row > $downarrowlen} {
4677 set termrow [expr {$row - $downarrowlen - 1}]
4678 foreach p [lindex $parentlist $termrow] {
4679 set i [lsearch -exact $idlist $p]
4680 if {$i < 0} continue
4681 set nr [nextuse $p $termrow]
4682 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4683 set idlist [lreplace $idlist $i $i]
4687 set col [lsearch -exact $idlist $id]
4689 set col [idcol $idlist $id]
4690 set idlist [linsert $idlist $col $id]
4691 if {$children($curview,$id) ne {}} {
4692 makeupline $id $rm1 $row $col
4695 set r [expr {$row + $uparrowlen - 1}]
4696 if {$r < $commitidx($curview)} {
4698 foreach p [lindex $parentlist $r] {
4699 if {[lsearch -exact $idlist $p] >= 0} continue
4700 set fk [lindex $children($curview,$p) 0]
4701 if {[rowofcommit $fk] < $row} {
4702 set x [idcol $idlist $p $x]
4703 set idlist [linsert $idlist $x $p]
4706 if {[incr r] < $commitidx($curview)} {
4707 set p [lindex $displayorder $r]
4708 if {[lsearch -exact $idlist $p] < 0} {
4709 set fk [lindex $children($curview,$p) 0]
4710 if {$fk ne {} && [rowofcommit $fk] < $row} {
4711 set x [idcol $idlist $p $x]
4712 set idlist [linsert $idlist $x $p]
4718 if {$final && !$viewcomplete($curview) &&
4719 $row + $uparrowlen + $mingaplen + $downarrowlen
4720 >= $commitidx($curview)} {
4723 set l [llength $rowidlist]
4725 lappend rowidlist $idlist
4727 lappend rowfinal $final
4728 } elseif {$row < $l} {
4729 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4730 lset rowidlist $row $idlist
4733 lset rowfinal $row $final
4735 set pad [ntimes [expr {$row - $l}] {}]
4736 set rowidlist [concat $rowidlist $pad]
4737 lappend rowidlist $idlist
4738 set rowfinal [concat $rowfinal $pad]
4739 lappend rowfinal $final
4740 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4746 proc changedrow {row} {
4747 global displayorder iddrawn rowisopt need_redisplay
4749 set l [llength $rowisopt]
4751 lset rowisopt $row 0
4752 if {$row + 1 < $l} {
4753 lset rowisopt [expr {$row + 1}] 0
4754 if {$row + 2 < $l} {
4755 lset rowisopt [expr {$row + 2}] 0
4759 set id [lindex $displayorder $row]
4760 if {[info exists iddrawn($id)]} {
4761 set need_redisplay 1
4765 proc insert_pad {row col npad} {
4768 set pad [ntimes $npad {}]
4769 set idlist [lindex $rowidlist $row]
4770 set bef [lrange $idlist 0 [expr {$col - 1}]]
4771 set aft [lrange $idlist $col end]
4772 set i [lsearch -exact $aft {}]
4774 set aft [lreplace $aft $i $i]
4776 lset rowidlist $row [concat $bef $pad $aft]
4780 proc optimize_rows {row col endrow} {
4781 global rowidlist rowisopt displayorder curview children
4786 for {} {$row < $endrow} {incr row; set col 0} {
4787 if {[lindex $rowisopt $row]} continue
4789 set y0 [expr {$row - 1}]
4790 set ym [expr {$row - 2}]
4791 set idlist [lindex $rowidlist $row]
4792 set previdlist [lindex $rowidlist $y0]
4793 if {$idlist eq {} || $previdlist eq {}} continue
4795 set pprevidlist [lindex $rowidlist $ym]
4796 if {$pprevidlist eq {}} continue
4802 for {} {$col < [llength $idlist]} {incr col} {
4803 set id [lindex $idlist $col]
4804 if {[lindex $previdlist $col] eq $id} continue
4809 set x0 [lsearch -exact $previdlist $id]
4810 if {$x0 < 0} continue
4811 set z [expr {$x0 - $col}]
4815 set xm [lsearch -exact $pprevidlist $id]
4817 set z0 [expr {$xm - $x0}]
4821 # if row y0 is the first child of $id then it's not an arrow
4822 if {[lindex $children($curview,$id) 0] ne
4823 [lindex $displayorder $y0]} {
4827 if {!$isarrow && $id ne [lindex $displayorder $row] &&
4828 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
4831 # Looking at lines from this row to the previous row,
4832 # make them go straight up if they end in an arrow on
4833 # the previous row; otherwise make them go straight up
4835 if {$z < -1 || ($z < 0 && $isarrow)} {
4836 # Line currently goes left too much;
4837 # insert pads in the previous row, then optimize it
4838 set npad [expr {-1 - $z + $isarrow}]
4839 insert_pad $y0 $x0 $npad
4841 optimize_rows $y0 $x0 $row
4843 set previdlist [lindex $rowidlist $y0]
4844 set x0 [lsearch -exact $previdlist $id]
4845 set z [expr {$x0 - $col}]
4847 set pprevidlist [lindex $rowidlist $ym]
4848 set xm [lsearch -exact $pprevidlist $id]
4849 set z0 [expr {$xm - $x0}]
4851 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
4852 # Line currently goes right too much;
4853 # insert pads in this line
4854 set npad [expr {$z - 1 + $isarrow}]
4855 insert_pad $row $col $npad
4856 set idlist [lindex $rowidlist $row]
4858 set z [expr {$x0 - $col}]
4861 if {$z0 eq {} && !$isarrow && $ym >= 0} {
4862 # this line links to its first child on row $row-2
4863 set id [lindex $displayorder $ym]
4864 set xc [lsearch -exact $pprevidlist $id]
4866 set z0 [expr {$xc - $x0}]
4869 # avoid lines jigging left then immediately right
4870 if {$z0 ne {} && $z < 0 && $z0 > 0} {
4871 insert_pad $y0 $x0 1
4873 optimize_rows $y0 $x0 $row
4874 set previdlist [lindex $rowidlist $y0]
4878 # Find the first column that doesn't have a line going right
4879 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
4880 set id [lindex $idlist $col]
4881 if {$id eq {}} break
4882 set x0 [lsearch -exact $previdlist $id]
4884 # check if this is the link to the first child
4885 set kid [lindex $displayorder $y0]
4886 if {[lindex $children($curview,$id) 0] eq $kid} {
4887 # it is, work out offset to child
4888 set x0 [lsearch -exact $previdlist $kid]
4891 if {$x0 <= $col} break
4893 # Insert a pad at that column as long as it has a line and
4894 # isn't the last column
4895 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
4896 set idlist [linsert $idlist $col {}]
4897 lset rowidlist $row $idlist
4905 global canvx0 linespc
4906 return [expr {$canvx0 + $col * $linespc}]
4910 global canvy0 linespc
4911 return [expr {$canvy0 + $row * $linespc}]
4914 proc linewidth {id} {
4915 global thickerline lthickness
4918 if {[info exists thickerline] && $id eq $thickerline} {
4919 set wid [expr {2 * $lthickness}]
4924 proc rowranges {id} {
4925 global curview children uparrowlen downarrowlen
4928 set kids $children($curview,$id)
4934 foreach child $kids {
4935 if {![commitinview $child $curview]} break
4936 set row [rowofcommit $child]
4937 if {![info exists prev]} {
4938 lappend ret [expr {$row + 1}]
4940 if {$row <= $prevrow} {
4941 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4943 # see if the line extends the whole way from prevrow to row
4944 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4945 [lsearch -exact [lindex $rowidlist \
4946 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4947 # it doesn't, see where it ends
4948 set r [expr {$prevrow + $downarrowlen}]
4949 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4950 while {[incr r -1] > $prevrow &&
4951 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4953 while {[incr r] <= $row &&
4954 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4958 # see where it starts up again
4959 set r [expr {$row - $uparrowlen}]
4960 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4961 while {[incr r] < $row &&
4962 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4964 while {[incr r -1] >= $prevrow &&
4965 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4971 if {$child eq $id} {
4980 proc drawlineseg {id row endrow arrowlow} {
4981 global rowidlist displayorder iddrawn linesegs
4982 global canv colormap linespc curview maxlinelen parentlist
4984 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4985 set le [expr {$row + 1}]
4988 set c [lsearch -exact [lindex $rowidlist $le] $id]
4994 set x [lindex $displayorder $le]
4999 if {[info exists iddrawn($x)] || $le == $endrow} {
5000 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5016 if {[info exists linesegs($id)]} {
5017 set lines $linesegs($id)
5019 set r0 [lindex $li 0]
5021 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5031 set li [lindex $lines [expr {$i-1}]]
5032 set r1 [lindex $li 1]
5033 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5038 set x [lindex $cols [expr {$le - $row}]]
5039 set xp [lindex $cols [expr {$le - 1 - $row}]]
5040 set dir [expr {$xp - $x}]
5042 set ith [lindex $lines $i 2]
5043 set coords [$canv coords $ith]
5044 set ah [$canv itemcget $ith -arrow]
5045 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5046 set x2 [lindex $cols [expr {$le + 1 - $row}]]
5047 if {$x2 ne {} && $x - $x2 == $dir} {
5048 set coords [lrange $coords 0 end-2]
5051 set coords [list [xc $le $x] [yc $le]]
5054 set itl [lindex $lines [expr {$i-1}] 2]
5055 set al [$canv itemcget $itl -arrow]
5056 set arrowlow [expr {$al eq "last" || $al eq "both"}]
5057 } elseif {$arrowlow} {
5058 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5059 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5063 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5064 for {set y $le} {[incr y -1] > $row} {} {
5066 set xp [lindex $cols [expr {$y - 1 - $row}]]
5067 set ndir [expr {$xp - $x}]
5068 if {$dir != $ndir || $xp < 0} {
5069 lappend coords [xc $y $x] [yc $y]
5075 # join parent line to first child
5076 set ch [lindex $displayorder $row]
5077 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5079 puts "oops: drawlineseg: child $ch not on row $row"
5080 } elseif {$xc != $x} {
5081 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5082 set d [expr {int(0.5 * $linespc)}]
5085 set x2 [expr {$x1 - $d}]
5087 set x2 [expr {$x1 + $d}]
5090 set y1 [expr {$y2 + $d}]
5091 lappend coords $x1 $y1 $x2 $y2
5092 } elseif {$xc < $x - 1} {
5093 lappend coords [xc $row [expr {$x-1}]] [yc $row]
5094 } elseif {$xc > $x + 1} {
5095 lappend coords [xc $row [expr {$x+1}]] [yc $row]
5099 lappend coords [xc $row $x] [yc $row]
5101 set xn [xc $row $xp]
5103 lappend coords $xn $yn
5107 set t [$canv create line $coords -width [linewidth $id] \
5108 -fill $colormap($id) -tags lines.$id -arrow $arrow]
5111 set lines [linsert $lines $i [list $row $le $t]]
5113 $canv coords $ith $coords
5114 if {$arrow ne $ah} {
5115 $canv itemconf $ith -arrow $arrow
5117 lset lines $i 0 $row
5120 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5121 set ndir [expr {$xo - $xp}]
5122 set clow [$canv coords $itl]
5123 if {$dir == $ndir} {
5124 set clow [lrange $clow 2 end]
5126 set coords [concat $coords $clow]
5128 lset lines [expr {$i-1}] 1 $le
5130 # coalesce two pieces
5132 set b [lindex $lines [expr {$i-1}] 0]
5133 set e [lindex $lines $i 1]
5134 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5136 $canv coords $itl $coords
5137 if {$arrow ne $al} {
5138 $canv itemconf $itl -arrow $arrow
5142 set linesegs($id) $lines
5146 proc drawparentlinks {id row} {
5147 global rowidlist canv colormap curview parentlist
5148 global idpos linespc
5150 set rowids [lindex $rowidlist $row]
5151 set col [lsearch -exact $rowids $id]
5152 if {$col < 0} return
5153 set olds [lindex $parentlist $row]
5154 set row2 [expr {$row + 1}]
5155 set x [xc $row $col]
5158 set d [expr {int(0.5 * $linespc)}]
5159 set ymid [expr {$y + $d}]
5160 set ids [lindex $rowidlist $row2]
5161 # rmx = right-most X coord used
5164 set i [lsearch -exact $ids $p]
5166 puts "oops, parent $p of $id not in list"
5169 set x2 [xc $row2 $i]
5173 set j [lsearch -exact $rowids $p]
5175 # drawlineseg will do this one for us
5179 # should handle duplicated parents here...
5180 set coords [list $x $y]
5182 # if attaching to a vertical segment, draw a smaller
5183 # slant for visual distinctness
5186 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5188 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5190 } elseif {$i < $col && $i < $j} {
5191 # segment slants towards us already
5192 lappend coords [xc $row $j] $y
5194 if {$i < $col - 1} {
5195 lappend coords [expr {$x2 + $linespc}] $y
5196 } elseif {$i > $col + 1} {
5197 lappend coords [expr {$x2 - $linespc}] $y
5199 lappend coords $x2 $y2
5202 lappend coords $x2 $y2
5204 set t [$canv create line $coords -width [linewidth $p] \
5205 -fill $colormap($p) -tags lines.$p]
5209 if {$rmx > [lindex $idpos($id) 1]} {
5210 lset idpos($id) 1 $rmx
5215 proc drawlines {id} {
5218 $canv itemconf lines.$id -width [linewidth $id]
5221 proc drawcmittext {id row col} {
5222 global linespc canv canv2 canv3 fgcolor curview
5223 global cmitlisted commitinfo rowidlist parentlist
5224 global rowtextx idpos idtags idheads idotherrefs
5225 global linehtag linentag linedtag selectedline
5226 global canvxmax boldrows boldnamerows fgcolor
5227 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5229 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5230 set listed $cmitlisted($curview,$id)
5231 if {$id eq $nullid} {
5233 } elseif {$id eq $nullid2} {
5235 } elseif {$id eq $mainheadid} {
5238 set ofill [lindex $circlecolors $listed]
5240 set x [xc $row $col]
5242 set orad [expr {$linespc / 3}]
5244 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5245 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5246 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5247 } elseif {$listed == 3} {
5248 # triangle pointing left for left-side commits
5249 set t [$canv create polygon \
5250 [expr {$x - $orad}] $y \
5251 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5252 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5253 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5255 # triangle pointing right for right-side commits
5256 set t [$canv create polygon \
5257 [expr {$x + $orad - 1}] $y \
5258 [expr {$x - $orad}] [expr {$y - $orad}] \
5259 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5260 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5262 set circleitem($row) $t
5264 $canv bind $t <1> {selcanvline {} %x %y}
5265 set rmx [llength [lindex $rowidlist $row]]
5266 set olds [lindex $parentlist $row]
5268 set nextids [lindex $rowidlist [expr {$row + 1}]]
5270 set i [lsearch -exact $nextids $p]
5276 set xt [xc $row $rmx]
5277 set rowtextx($row) $xt
5278 set idpos($id) [list $x $xt $y]
5279 if {[info exists idtags($id)] || [info exists idheads($id)]
5280 || [info exists idotherrefs($id)]} {
5281 set xt [drawtags $id $x $xt $y]
5283 set headline [lindex $commitinfo($id) 0]
5284 set name [lindex $commitinfo($id) 1]
5285 set date [lindex $commitinfo($id) 2]
5286 set date [formatdate $date]
5289 set isbold [ishighlighted $id]
5291 lappend boldrows $row
5292 set font mainfontbold
5294 lappend boldnamerows $row
5295 set nfont mainfontbold
5298 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
5299 -text $headline -font $font -tags text]
5300 $canv bind $linehtag($row) $ctxbut "rowmenu %X %Y $id"
5301 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5302 -text $name -font $nfont -tags text]
5303 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5304 -text $date -font mainfont -tags text]
5305 if {$selectedline == $row} {
5308 set xr [expr {$xt + [font measure $font $headline]}]
5309 if {$xr > $canvxmax} {
5315 proc drawcmitrow {row} {
5316 global displayorder rowidlist nrows_drawn
5317 global iddrawn markingmatches
5318 global commitinfo numcommits
5319 global filehighlight fhighlights findpattern nhighlights
5320 global hlview vhighlights
5321 global highlight_related rhighlights
5323 if {$row >= $numcommits} return
5325 set id [lindex $displayorder $row]
5326 if {[info exists hlview] && ![info exists vhighlights($id)]} {
5327 askvhighlight $row $id
5329 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5330 askfilehighlight $row $id
5332 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5333 askfindhighlight $row $id
5335 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5336 askrelhighlight $row $id
5338 if {![info exists iddrawn($id)]} {
5339 set col [lsearch -exact [lindex $rowidlist $row] $id]
5341 puts "oops, row $row id $id not in list"
5344 if {![info exists commitinfo($id)]} {
5348 drawcmittext $id $row $col
5352 if {$markingmatches} {
5353 markrowmatches $row $id
5357 proc drawcommits {row {endrow {}}} {
5358 global numcommits iddrawn displayorder curview need_redisplay
5359 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5364 if {$endrow eq {}} {
5367 if {$endrow >= $numcommits} {
5368 set endrow [expr {$numcommits - 1}]
5371 set rl1 [expr {$row - $downarrowlen - 3}]
5375 set ro1 [expr {$row - 3}]
5379 set r2 [expr {$endrow + $uparrowlen + 3}]
5380 if {$r2 > $numcommits} {
5383 for {set r $rl1} {$r < $r2} {incr r} {
5384 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5388 set rl1 [expr {$r + 1}]
5394 optimize_rows $ro1 0 $r2
5395 if {$need_redisplay || $nrows_drawn > 2000} {
5400 # make the lines join to already-drawn rows either side
5401 set r [expr {$row - 1}]
5402 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5405 set er [expr {$endrow + 1}]
5406 if {$er >= $numcommits ||
5407 ![info exists iddrawn([lindex $displayorder $er])]} {
5410 for {} {$r <= $er} {incr r} {
5411 set id [lindex $displayorder $r]
5412 set wasdrawn [info exists iddrawn($id)]
5414 if {$r == $er} break
5415 set nextid [lindex $displayorder [expr {$r + 1}]]
5416 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5417 drawparentlinks $id $r
5419 set rowids [lindex $rowidlist $r]
5420 foreach lid $rowids {
5421 if {$lid eq {}} continue
5422 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5424 # see if this is the first child of any of its parents
5425 foreach p [lindex $parentlist $r] {
5426 if {[lsearch -exact $rowids $p] < 0} {
5427 # make this line extend up to the child
5428 set lineend($p) [drawlineseg $p $r $er 0]
5432 set lineend($lid) [drawlineseg $lid $r $er 1]
5438 proc undolayout {row} {
5439 global uparrowlen mingaplen downarrowlen
5440 global rowidlist rowisopt rowfinal need_redisplay
5442 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5446 if {[llength $rowidlist] > $r} {
5448 set rowidlist [lrange $rowidlist 0 $r]
5449 set rowfinal [lrange $rowfinal 0 $r]
5450 set rowisopt [lrange $rowisopt 0 $r]
5451 set need_redisplay 1
5456 proc drawvisible {} {
5457 global canv linespc curview vrowmod selectedline targetrow targetid
5458 global need_redisplay cscroll numcommits
5460 set fs [$canv yview]
5461 set ymax [lindex [$canv cget -scrollregion] 3]
5462 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5463 set f0 [lindex $fs 0]
5464 set f1 [lindex $fs 1]
5465 set y0 [expr {int($f0 * $ymax)}]
5466 set y1 [expr {int($f1 * $ymax)}]
5468 if {[info exists targetid]} {
5469 if {[commitinview $targetid $curview]} {
5470 set r [rowofcommit $targetid]
5471 if {$r != $targetrow} {
5472 # Fix up the scrollregion and change the scrolling position
5473 # now that our target row has moved.
5474 set diff [expr {($r - $targetrow) * $linespc}]
5477 set ymax [lindex [$canv cget -scrollregion] 3]
5480 set f0 [expr {$y0 / $ymax}]
5481 set f1 [expr {$y1 / $ymax}]
5482 allcanvs yview moveto $f0
5483 $cscroll set $f0 $f1
5484 set need_redisplay 1
5491 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5492 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5493 if {$endrow >= $vrowmod($curview)} {
5494 update_arcrows $curview
5496 if {$selectedline ne {} &&
5497 $row <= $selectedline && $selectedline <= $endrow} {
5498 set targetrow $selectedline
5499 } elseif {[info exists targetid]} {
5500 set targetrow [expr {int(($row + $endrow) / 2)}]
5502 if {[info exists targetrow]} {
5503 if {$targetrow >= $numcommits} {
5504 set targetrow [expr {$numcommits - 1}]
5506 set targetid [commitonrow $targetrow]
5508 drawcommits $row $endrow
5511 proc clear_display {} {
5512 global iddrawn linesegs need_redisplay nrows_drawn
5513 global vhighlights fhighlights nhighlights rhighlights
5514 global linehtag linentag linedtag boldrows boldnamerows
5517 catch {unset iddrawn}
5518 catch {unset linesegs}
5519 catch {unset linehtag}
5520 catch {unset linentag}
5521 catch {unset linedtag}
5524 catch {unset vhighlights}
5525 catch {unset fhighlights}
5526 catch {unset nhighlights}
5527 catch {unset rhighlights}
5528 set need_redisplay 0
5532 proc findcrossings {id} {
5533 global rowidlist parentlist numcommits displayorder
5537 foreach {s e} [rowranges $id] {
5538 if {$e >= $numcommits} {
5539 set e [expr {$numcommits - 1}]
5541 if {$e <= $s} continue
5542 for {set row $e} {[incr row -1] >= $s} {} {
5543 set x [lsearch -exact [lindex $rowidlist $row] $id]
5545 set olds [lindex $parentlist $row]
5546 set kid [lindex $displayorder $row]
5547 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5548 if {$kidx < 0} continue
5549 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5551 set px [lsearch -exact $nextrow $p]
5552 if {$px < 0} continue
5553 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5554 if {[lsearch -exact $ccross $p] >= 0} continue
5555 if {$x == $px + ($kidx < $px? -1: 1)} {
5557 } elseif {[lsearch -exact $cross $p] < 0} {
5564 return [concat $ccross {{}} $cross]
5567 proc assigncolor {id} {
5568 global colormap colors nextcolor
5569 global parents children children curview
5571 if {[info exists colormap($id)]} return
5572 set ncolors [llength $colors]
5573 if {[info exists children($curview,$id)]} {
5574 set kids $children($curview,$id)
5578 if {[llength $kids] == 1} {
5579 set child [lindex $kids 0]
5580 if {[info exists colormap($child)]
5581 && [llength $parents($curview,$child)] == 1} {
5582 set colormap($id) $colormap($child)
5588 foreach x [findcrossings $id] {
5590 # delimiter between corner crossings and other crossings
5591 if {[llength $badcolors] >= $ncolors - 1} break
5592 set origbad $badcolors
5594 if {[info exists colormap($x)]
5595 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5596 lappend badcolors $colormap($x)
5599 if {[llength $badcolors] >= $ncolors} {
5600 set badcolors $origbad
5602 set origbad $badcolors
5603 if {[llength $badcolors] < $ncolors - 1} {
5604 foreach child $kids {
5605 if {[info exists colormap($child)]
5606 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5607 lappend badcolors $colormap($child)
5609 foreach p $parents($curview,$child) {
5610 if {[info exists colormap($p)]
5611 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5612 lappend badcolors $colormap($p)
5616 if {[llength $badcolors] >= $ncolors} {
5617 set badcolors $origbad
5620 for {set i 0} {$i <= $ncolors} {incr i} {
5621 set c [lindex $colors $nextcolor]
5622 if {[incr nextcolor] >= $ncolors} {
5625 if {[lsearch -exact $badcolors $c]} break
5627 set colormap($id) $c
5630 proc bindline {t id} {
5633 $canv bind $t <Enter> "lineenter %x %y $id"
5634 $canv bind $t <Motion> "linemotion %x %y $id"
5635 $canv bind $t <Leave> "lineleave $id"
5636 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5639 proc drawtags {id x xt y1} {
5640 global idtags idheads idotherrefs mainhead
5641 global linespc lthickness
5642 global canv rowtextx curview fgcolor bgcolor ctxbut
5647 if {[info exists idtags($id)]} {
5648 set marks $idtags($id)
5649 set ntags [llength $marks]
5651 if {[info exists idheads($id)]} {
5652 set marks [concat $marks $idheads($id)]
5653 set nheads [llength $idheads($id)]
5655 if {[info exists idotherrefs($id)]} {
5656 set marks [concat $marks $idotherrefs($id)]
5662 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5663 set yt [expr {$y1 - 0.5 * $linespc}]
5664 set yb [expr {$yt + $linespc - 1}]
5668 foreach tag $marks {
5670 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5671 set wid [font measure mainfontbold $tag]
5673 set wid [font measure mainfont $tag]
5677 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5679 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5680 -width $lthickness -fill black -tags tag.$id]
5682 foreach tag $marks x $xvals wid $wvals {
5683 set xl [expr {$x + $delta}]
5684 set xr [expr {$x + $delta + $wid + $lthickness}]
5686 if {[incr ntags -1] >= 0} {
5688 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5689 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5690 -width 1 -outline black -fill yellow -tags tag.$id]
5691 $canv bind $t <1> [list showtag $tag 1]
5692 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5694 # draw a head or other ref
5695 if {[incr nheads -1] >= 0} {
5697 if {$tag eq $mainhead} {
5698 set font mainfontbold
5703 set xl [expr {$xl - $delta/2}]
5704 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5705 -width 1 -outline black -fill $col -tags tag.$id
5706 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5707 set rwid [font measure mainfont $remoteprefix]
5708 set xi [expr {$x + 1}]
5709 set yti [expr {$yt + 1}]
5710 set xri [expr {$x + $rwid}]
5711 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5712 -width 0 -fill "#ffddaa" -tags tag.$id
5715 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5716 -font $font -tags [list tag.$id text]]
5718 $canv bind $t <1> [list showtag $tag 1]
5719 } elseif {$nheads >= 0} {
5720 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
5726 proc xcoord {i level ln} {
5727 global canvx0 xspc1 xspc2
5729 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5730 if {$i > 0 && $i == $level} {
5731 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5732 } elseif {$i > $level} {
5733 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5738 proc show_status {msg} {
5742 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5743 -tags text -fill $fgcolor
5746 # Don't change the text pane cursor if it is currently the hand cursor,
5747 # showing that we are over a sha1 ID link.
5748 proc settextcursor {c} {
5749 global ctext curtextcursor
5751 if {[$ctext cget -cursor] == $curtextcursor} {
5752 $ctext config -cursor $c
5754 set curtextcursor $c
5757 proc nowbusy {what {name {}}} {
5758 global isbusy busyname statusw
5760 if {[array names isbusy] eq {}} {
5761 . config -cursor watch
5765 set busyname($what) $name
5767 $statusw conf -text $name
5771 proc notbusy {what} {
5772 global isbusy maincursor textcursor busyname statusw
5776 if {$busyname($what) ne {} &&
5777 [$statusw cget -text] eq $busyname($what)} {
5778 $statusw conf -text {}
5781 if {[array names isbusy] eq {}} {
5782 . config -cursor $maincursor
5783 settextcursor $textcursor
5787 proc findmatches {f} {
5788 global findtype findstring
5789 if {$findtype == [mc "Regexp"]} {
5790 set matches [regexp -indices -all -inline $findstring $f]
5793 if {$findtype == [mc "IgnCase"]} {
5794 set f [string tolower $f]
5795 set fs [string tolower $fs]
5799 set l [string length $fs]
5800 while {[set j [string first $fs $f $i]] >= 0} {
5801 lappend matches [list $j [expr {$j+$l-1}]]
5802 set i [expr {$j + $l}]
5808 proc dofind {{dirn 1} {wrap 1}} {
5809 global findstring findstartline findcurline selectedline numcommits
5810 global gdttype filehighlight fh_serial find_dirn findallowwrap
5812 if {[info exists find_dirn]} {
5813 if {$find_dirn == $dirn} return
5817 if {$findstring eq {} || $numcommits == 0} return
5818 if {$selectedline eq {}} {
5819 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
5821 set findstartline $selectedline
5823 set findcurline $findstartline
5824 nowbusy finding [mc "Searching"]
5825 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
5826 after cancel do_file_hl $fh_serial
5827 do_file_hl $fh_serial
5830 set findallowwrap $wrap
5834 proc stopfinding {} {
5835 global find_dirn findcurline fprogcoord
5837 if {[info exists find_dirn]} {
5848 global commitdata commitinfo numcommits findpattern findloc
5849 global findstartline findcurline findallowwrap
5850 global find_dirn gdttype fhighlights fprogcoord
5851 global curview varcorder vrownum varccommits vrowmod
5853 if {![info exists find_dirn]} {
5856 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
5859 if {$find_dirn > 0} {
5861 if {$l >= $numcommits} {
5864 if {$l <= $findstartline} {
5865 set lim [expr {$findstartline + 1}]
5868 set moretodo $findallowwrap
5875 if {$l >= $findstartline} {
5876 set lim [expr {$findstartline - 1}]
5879 set moretodo $findallowwrap
5882 set n [expr {($lim - $l) * $find_dirn}]
5887 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
5888 update_arcrows $curview
5892 set ai [bsearch $vrownum($curview) $l]
5893 set a [lindex $varcorder($curview) $ai]
5894 set arow [lindex $vrownum($curview) $ai]
5895 set ids [lindex $varccommits($curview,$a)]
5896 set arowend [expr {$arow + [llength $ids]}]
5897 if {$gdttype eq [mc "containing:"]} {
5898 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5899 if {$l < $arow || $l >= $arowend} {
5901 set a [lindex $varcorder($curview) $ai]
5902 set arow [lindex $vrownum($curview) $ai]
5903 set ids [lindex $varccommits($curview,$a)]
5904 set arowend [expr {$arow + [llength $ids]}]
5906 set id [lindex $ids [expr {$l - $arow}]]
5907 # shouldn't happen unless git log doesn't give all the commits...
5908 if {![info exists commitdata($id)] ||
5909 ![doesmatch $commitdata($id)]} {
5912 if {![info exists commitinfo($id)]} {
5915 set info $commitinfo($id)
5916 foreach f $info ty $fldtypes {
5917 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
5926 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5927 if {$l < $arow || $l >= $arowend} {
5929 set a [lindex $varcorder($curview) $ai]
5930 set arow [lindex $vrownum($curview) $ai]
5931 set ids [lindex $varccommits($curview,$a)]
5932 set arowend [expr {$arow + [llength $ids]}]
5934 set id [lindex $ids [expr {$l - $arow}]]
5935 if {![info exists fhighlights($id)]} {
5936 # this sets fhighlights($id) to -1
5937 askfilehighlight $l $id
5939 if {$fhighlights($id) > 0} {
5943 if {$fhighlights($id) < 0} {
5946 set findcurline [expr {$l - $find_dirn}]
5951 if {$found || ($domore && !$moretodo)} {
5967 set findcurline [expr {$l - $find_dirn}]
5969 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
5973 set fprogcoord [expr {$n * 1.0 / $numcommits}]
5978 proc findselectline {l} {
5979 global findloc commentend ctext findcurline markingmatches gdttype
5981 set markingmatches 1
5984 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
5985 # highlight the matches in the comments
5986 set f [$ctext get 1.0 $commentend]
5987 set matches [findmatches $f]
5988 foreach match $matches {
5989 set start [lindex $match 0]
5990 set end [expr {[lindex $match 1] + 1}]
5991 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5997 # mark the bits of a headline or author that match a find string
5998 proc markmatches {canv l str tag matches font row} {
6001 set bbox [$canv bbox $tag]
6002 set x0 [lindex $bbox 0]
6003 set y0 [lindex $bbox 1]
6004 set y1 [lindex $bbox 3]
6005 foreach match $matches {
6006 set start [lindex $match 0]
6007 set end [lindex $match 1]
6008 if {$start > $end} continue
6009 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6010 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6011 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6012 [expr {$x0+$xlen+2}] $y1 \
6013 -outline {} -tags [list match$l matches] -fill yellow]
6015 if {$row == $selectedline} {
6016 $canv raise $t secsel
6021 proc unmarkmatches {} {
6022 global markingmatches
6024 allcanvs delete matches
6025 set markingmatches 0
6029 proc selcanvline {w x y} {
6030 global canv canvy0 ctext linespc
6032 set ymax [lindex [$canv cget -scrollregion] 3]
6033 if {$ymax == {}} return
6034 set yfrac [lindex [$canv yview] 0]
6035 set y [expr {$y + $yfrac * $ymax}]
6036 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6041 set xmax [lindex [$canv cget -scrollregion] 2]
6042 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6043 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6049 proc commit_descriptor {p} {
6051 if {![info exists commitinfo($p)]} {
6055 if {[llength $commitinfo($p)] > 1} {
6056 set l [lindex $commitinfo($p) 0]
6061 # append some text to the ctext widget, and make any SHA1 ID
6062 # that we know about be a clickable link.
6063 proc appendwithlinks {text tags} {
6064 global ctext linknum curview
6066 set start [$ctext index "end - 1c"]
6067 $ctext insert end $text $tags
6068 set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6072 set linkid [string range $text $s $e]
6074 $ctext tag delete link$linknum
6075 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6076 setlink $linkid link$linknum
6081 proc setlink {id lk} {
6082 global curview ctext pendinglinks
6085 if {[string length $id] < 40} {
6086 set matches [longid $id]
6087 if {[llength $matches] > 0} {
6088 if {[llength $matches] > 1} return
6090 set id [lindex $matches 0]
6093 set known [commitinview $id $curview]
6096 $ctext tag conf $lk -foreground blue -underline 1
6097 $ctext tag bind $lk <1> [list selbyid $id]
6098 $ctext tag bind $lk <Enter> {linkcursor %W 1}
6099 $ctext tag bind $lk <Leave> {linkcursor %W -1}
6101 lappend pendinglinks($id) $lk
6102 interestedin $id {makelink %P}
6106 proc makelink {id} {
6109 if {![info exists pendinglinks($id)]} return
6110 foreach lk $pendinglinks($id) {
6113 unset pendinglinks($id)
6116 proc linkcursor {w inc} {
6117 global linkentercount curtextcursor
6119 if {[incr linkentercount $inc] > 0} {
6120 $w configure -cursor hand2
6122 $w configure -cursor $curtextcursor
6123 if {$linkentercount < 0} {
6124 set linkentercount 0
6129 proc viewnextline {dir} {
6133 set ymax [lindex [$canv cget -scrollregion] 3]
6134 set wnow [$canv yview]
6135 set wtop [expr {[lindex $wnow 0] * $ymax}]
6136 set newtop [expr {$wtop + $dir * $linespc}]
6139 } elseif {$newtop > $ymax} {
6142 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6145 # add a list of tag or branch names at position pos
6146 # returns the number of names inserted
6147 proc appendrefs {pos ids var} {
6148 global ctext linknum curview $var maxrefs
6150 if {[catch {$ctext index $pos}]} {
6153 $ctext conf -state normal
6154 $ctext delete $pos "$pos lineend"
6157 foreach tag [set $var\($id\)] {
6158 lappend tags [list $tag $id]
6161 if {[llength $tags] > $maxrefs} {
6162 $ctext insert $pos "many ([llength $tags])"
6164 set tags [lsort -index 0 -decreasing $tags]
6167 set id [lindex $ti 1]
6170 $ctext tag delete $lk
6171 $ctext insert $pos $sep
6172 $ctext insert $pos [lindex $ti 0] $lk
6177 $ctext conf -state disabled
6178 return [llength $tags]
6181 # called when we have finished computing the nearby tags
6182 proc dispneartags {delay} {
6183 global selectedline currentid showneartags tagphase
6185 if {$selectedline eq {} || !$showneartags} return
6186 after cancel dispnexttag
6188 after 200 dispnexttag
6191 after idle dispnexttag
6196 proc dispnexttag {} {
6197 global selectedline currentid showneartags tagphase ctext
6199 if {$selectedline eq {} || !$showneartags} return
6200 switch -- $tagphase {
6202 set dtags [desctags $currentid]
6204 appendrefs precedes $dtags idtags
6208 set atags [anctags $currentid]
6210 appendrefs follows $atags idtags
6214 set dheads [descheads $currentid]
6215 if {$dheads ne {}} {
6216 if {[appendrefs branch $dheads idheads] > 1
6217 && [$ctext get "branch -3c"] eq "h"} {
6218 # turn "Branch" into "Branches"
6219 $ctext conf -state normal
6220 $ctext insert "branch -2c" "es"
6221 $ctext conf -state disabled
6226 if {[incr tagphase] <= 2} {
6227 after idle dispnexttag
6231 proc make_secsel {l} {
6232 global linehtag linentag linedtag canv canv2 canv3
6234 if {![info exists linehtag($l)]} return
6236 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
6237 -tags secsel -fill [$canv cget -selectbackground]]
6239 $canv2 delete secsel
6240 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
6241 -tags secsel -fill [$canv2 cget -selectbackground]]
6243 $canv3 delete secsel
6244 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
6245 -tags secsel -fill [$canv3 cget -selectbackground]]
6249 proc selectline {l isnew {desired_loc {}}} {
6250 global canv ctext commitinfo selectedline
6251 global canvy0 linespc parents children curview
6252 global currentid sha1entry
6253 global commentend idtags linknum
6254 global mergemax numcommits pending_select
6255 global cmitmode showneartags allcommits
6256 global targetrow targetid lastscrollrows
6257 global autoselect jump_to_here
6259 catch {unset pending_select}
6264 if {$l < 0 || $l >= $numcommits} return
6265 set id [commitonrow $l]
6270 if {$lastscrollrows < $numcommits} {
6274 set y [expr {$canvy0 + $l * $linespc}]
6275 set ymax [lindex [$canv cget -scrollregion] 3]
6276 set ytop [expr {$y - $linespc - 1}]
6277 set ybot [expr {$y + $linespc + 1}]
6278 set wnow [$canv yview]
6279 set wtop [expr {[lindex $wnow 0] * $ymax}]
6280 set wbot [expr {[lindex $wnow 1] * $ymax}]
6281 set wh [expr {$wbot - $wtop}]
6283 if {$ytop < $wtop} {
6284 if {$ybot < $wtop} {
6285 set newtop [expr {$y - $wh / 2.0}]
6288 if {$newtop > $wtop - $linespc} {
6289 set newtop [expr {$wtop - $linespc}]
6292 } elseif {$ybot > $wbot} {
6293 if {$ytop > $wbot} {
6294 set newtop [expr {$y - $wh / 2.0}]
6296 set newtop [expr {$ybot - $wh}]
6297 if {$newtop < $wtop + $linespc} {
6298 set newtop [expr {$wtop + $linespc}]
6302 if {$newtop != $wtop} {
6306 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6313 addtohistory [list selbyid $id]
6316 $sha1entry delete 0 end
6317 $sha1entry insert 0 $id
6319 $sha1entry selection from 0
6320 $sha1entry selection to end
6324 $ctext conf -state normal
6327 if {![info exists commitinfo($id)]} {
6330 set info $commitinfo($id)
6331 set date [formatdate [lindex $info 2]]
6332 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
6333 set date [formatdate [lindex $info 4]]
6334 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
6335 if {[info exists idtags($id)]} {
6336 $ctext insert end [mc "Tags:"]
6337 foreach tag $idtags($id) {
6338 $ctext insert end " $tag"
6340 $ctext insert end "\n"
6344 set olds $parents($curview,$id)
6345 if {[llength $olds] > 1} {
6348 if {$np >= $mergemax} {
6353 $ctext insert end "[mc "Parent"]: " $tag
6354 appendwithlinks [commit_descriptor $p] {}
6359 append headers "[mc "Parent"]: [commit_descriptor $p]"
6363 foreach c $children($curview,$id) {
6364 append headers "[mc "Child"]: [commit_descriptor $c]"
6367 # make anything that looks like a SHA1 ID be a clickable link
6368 appendwithlinks $headers {}
6369 if {$showneartags} {
6370 if {![info exists allcommits]} {
6373 $ctext insert end "[mc "Branch"]: "
6374 $ctext mark set branch "end -1c"
6375 $ctext mark gravity branch left
6376 $ctext insert end "\n[mc "Follows"]: "
6377 $ctext mark set follows "end -1c"
6378 $ctext mark gravity follows left
6379 $ctext insert end "\n[mc "Precedes"]: "
6380 $ctext mark set precedes "end -1c"
6381 $ctext mark gravity precedes left
6382 $ctext insert end "\n"
6385 $ctext insert end "\n"
6386 set comment [lindex $info 5]
6387 if {[string first "\r" $comment] >= 0} {
6388 set comment [string map {"\r" "\n "} $comment]
6390 appendwithlinks $comment {comment}
6392 $ctext tag remove found 1.0 end
6393 $ctext conf -state disabled
6394 set commentend [$ctext index "end - 1c"]
6396 set jump_to_here $desired_loc
6397 init_flist [mc "Comments"]
6398 if {$cmitmode eq "tree"} {
6400 } elseif {[llength $olds] <= 1} {
6407 proc selfirstline {} {
6412 proc sellastline {} {
6415 set l [expr {$numcommits - 1}]
6419 proc selnextline {dir} {
6422 if {$selectedline eq {}} return
6423 set l [expr {$selectedline + $dir}]
6428 proc selnextpage {dir} {
6429 global canv linespc selectedline numcommits
6431 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6435 allcanvs yview scroll [expr {$dir * $lpp}] units
6437 if {$selectedline eq {}} return
6438 set l [expr {$selectedline + $dir * $lpp}]
6441 } elseif {$l >= $numcommits} {
6442 set l [expr $numcommits - 1]
6448 proc unselectline {} {
6449 global selectedline currentid
6452 catch {unset currentid}
6453 allcanvs delete secsel
6457 proc reselectline {} {
6460 if {$selectedline ne {}} {
6461 selectline $selectedline 0
6465 proc addtohistory {cmd} {
6466 global history historyindex curview
6468 set elt [list $curview $cmd]
6469 if {$historyindex > 0
6470 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6474 if {$historyindex < [llength $history]} {
6475 set history [lreplace $history $historyindex end $elt]
6477 lappend history $elt
6480 if {$historyindex > 1} {
6481 .tf.bar.leftbut conf -state normal
6483 .tf.bar.leftbut conf -state disabled
6485 .tf.bar.rightbut conf -state disabled
6491 set view [lindex $elt 0]
6492 set cmd [lindex $elt 1]
6493 if {$curview != $view} {
6500 global history historyindex
6503 if {$historyindex > 1} {
6504 incr historyindex -1
6505 godo [lindex $history [expr {$historyindex - 1}]]
6506 .tf.bar.rightbut conf -state normal
6508 if {$historyindex <= 1} {
6509 .tf.bar.leftbut conf -state disabled
6514 global history historyindex
6517 if {$historyindex < [llength $history]} {
6518 set cmd [lindex $history $historyindex]
6521 .tf.bar.leftbut conf -state normal
6523 if {$historyindex >= [llength $history]} {
6524 .tf.bar.rightbut conf -state disabled
6529 global treefilelist treeidlist diffids diffmergeid treepending
6530 global nullid nullid2
6533 catch {unset diffmergeid}
6534 if {![info exists treefilelist($id)]} {
6535 if {![info exists treepending]} {
6536 if {$id eq $nullid} {
6537 set cmd [list | git ls-files]
6538 } elseif {$id eq $nullid2} {
6539 set cmd [list | git ls-files --stage -t]
6541 set cmd [list | git ls-tree -r $id]
6543 if {[catch {set gtf [open $cmd r]}]} {
6547 set treefilelist($id) {}
6548 set treeidlist($id) {}
6549 fconfigure $gtf -blocking 0 -encoding binary
6550 filerun $gtf [list gettreeline $gtf $id]
6557 proc gettreeline {gtf id} {
6558 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6561 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6562 if {$diffids eq $nullid} {
6565 set i [string first "\t" $line]
6566 if {$i < 0} continue
6567 set fname [string range $line [expr {$i+1}] end]
6568 set line [string range $line 0 [expr {$i-1}]]
6569 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6570 set sha1 [lindex $line 2]
6571 lappend treeidlist($id) $sha1
6573 if {[string index $fname 0] eq "\""} {
6574 set fname [lindex $fname 0]
6576 set fname [encoding convertfrom $fname]
6577 lappend treefilelist($id) $fname
6580 return [expr {$nl >= 1000? 2: 1}]
6584 if {$cmitmode ne "tree"} {
6585 if {![info exists diffmergeid]} {
6586 gettreediffs $diffids
6588 } elseif {$id ne $diffids} {
6597 global treefilelist treeidlist diffids nullid nullid2
6598 global ctext_file_names ctext_file_lines
6599 global ctext commentend
6601 set i [lsearch -exact $treefilelist($diffids) $f]
6603 puts "oops, $f not in list for id $diffids"
6606 if {$diffids eq $nullid} {
6607 if {[catch {set bf [open $f r]} err]} {
6608 puts "oops, can't read $f: $err"
6612 set blob [lindex $treeidlist($diffids) $i]
6613 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6614 puts "oops, error reading blob $blob: $err"
6618 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
6619 filerun $bf [list getblobline $bf $diffids]
6620 $ctext config -state normal
6621 clear_ctext $commentend
6622 lappend ctext_file_names $f
6623 lappend ctext_file_lines [lindex [split $commentend "."] 0]
6624 $ctext insert end "\n"
6625 $ctext insert end "$f\n" filesep
6626 $ctext config -state disabled
6627 $ctext yview $commentend
6631 proc getblobline {bf id} {
6632 global diffids cmitmode ctext
6634 if {$id ne $diffids || $cmitmode ne "tree"} {
6638 $ctext config -state normal
6640 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6641 $ctext insert end "$line\n"
6644 global jump_to_here ctext_file_names commentend
6646 # delete last newline
6647 $ctext delete "end - 2c" "end - 1c"
6649 if {$jump_to_here ne {} &&
6650 [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
6651 set lnum [expr {[lindex $jump_to_here 1] +
6652 [lindex [split $commentend .] 0]}]
6653 mark_ctext_line $lnum
6657 $ctext config -state disabled
6658 return [expr {$nl >= 1000? 2: 1}]
6661 proc mark_ctext_line {lnum} {
6662 global ctext markbgcolor
6664 $ctext tag delete omark
6665 $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
6666 $ctext tag conf omark -background $markbgcolor
6670 proc mergediff {id} {
6671 global diffmergeid mdifffd
6672 global diffids treediffs
6676 global limitdiffs vfilelimit curview
6681 set treediffs($id) {}
6683 # this doesn't seem to actually affect anything...
6684 set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
6685 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6686 set cmd [concat $cmd -- $vfilelimit($curview)]
6688 if {[catch {set mdf [open $cmd r]} err]} {
6689 error_popup "[mc "Error getting merge diffs:"] $err"
6692 fconfigure $mdf -blocking 0 -encoding binary
6693 set mdifffd($id) $mdf
6694 set np [llength $parents($curview,$id)]
6695 set diffencoding [get_path_encoding {}]
6697 filerun $mdf [list getmergediffline $mdf $id $np]
6700 proc getmergediffline {mdf id np} {
6701 global diffmergeid ctext cflist mergemax
6702 global difffilestart mdifffd treediffs
6703 global ctext_file_names ctext_file_lines
6704 global diffencoding jump_to_here targetline diffline
6706 $ctext conf -state normal
6708 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
6709 if {![info exists diffmergeid] || $id != $diffmergeid
6710 || $mdf != $mdifffd($id)} {
6714 if {[regexp {^diff --cc (.*)} $line match fname]} {
6715 # start of a new file
6716 set fname [encoding convertfrom $fname]
6717 $ctext insert end "\n"
6718 set here [$ctext index "end - 1c"]
6719 lappend difffilestart $here
6720 lappend treediffs($id) $fname
6721 add_flist [list $fname]
6722 lappend ctext_file_names $fname
6723 lappend ctext_file_lines [lindex [split $here "."] 0]
6724 set diffencoding [get_path_encoding $fname]
6725 set l [expr {(78 - [string length $fname]) / 2}]
6726 set pad [string range "----------------------------------------" 1 $l]
6727 $ctext insert end "$pad $fname $pad\n" filesep
6729 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
6730 set targetline [lindex $jump_to_here 1]
6733 } elseif {[regexp {^@@} $line]} {
6734 set line [encoding convertfrom $diffencoding $line]
6735 $ctext insert end "$line\n" hunksep
6736 if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
6739 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
6742 set line [encoding convertfrom $diffencoding $line]
6743 # parse the prefix - one ' ', '-' or '+' for each parent
6748 for {set j 0} {$j < $np} {incr j} {
6749 set c [string range $line $j $j]
6752 } elseif {$c == "-"} {
6754 } elseif {$c == "+"} {
6763 if {!$isbad && $minuses ne {} && $pluses eq {}} {
6764 # line doesn't appear in result, parents in $minuses have the line
6765 set num [lindex $minuses 0]
6766 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
6767 # line appears in result, parents in $pluses don't have the line
6768 lappend tags mresult
6769 set num [lindex $spaces 0]
6772 if {$num >= $mergemax} {
6777 $ctext insert end "$line\n" $tags
6778 if {$targetline ne {} && $minuses eq {}} {
6779 if {$diffline == $targetline} {
6780 set here [$ctext index "end - 1 line"]
6781 mark_ctext_line [lindex [split $here .] 0]
6789 $ctext conf -state disabled
6794 return [expr {$nr >= 1000? 2: 1}]
6797 proc startdiff {ids} {
6798 global treediffs diffids treepending diffmergeid nullid nullid2
6802 catch {unset diffmergeid}
6803 if {![info exists treediffs($ids)] ||
6804 [lsearch -exact $ids $nullid] >= 0 ||
6805 [lsearch -exact $ids $nullid2] >= 0} {
6806 if {![info exists treepending]} {
6814 proc path_filter {filter name} {
6816 set l [string length $p]
6817 if {[string index $p end] eq "/"} {
6818 if {[string compare -length $l $p $name] == 0} {
6822 if {[string compare -length $l $p $name] == 0 &&
6823 ([string length $name] == $l ||
6824 [string index $name $l] eq "/")} {
6832 proc addtocflist {ids} {
6835 add_flist $treediffs($ids)
6839 proc diffcmd {ids flags} {
6840 global nullid nullid2
6842 set i [lsearch -exact $ids $nullid]
6843 set j [lsearch -exact $ids $nullid2]
6845 if {[llength $ids] > 1 && $j < 0} {
6846 # comparing working directory with some specific revision
6847 set cmd [concat | git diff-index $flags]
6849 lappend cmd -R [lindex $ids 1]
6851 lappend cmd [lindex $ids 0]
6854 # comparing working directory with index
6855 set cmd [concat | git diff-files $flags]
6860 } elseif {$j >= 0} {
6861 set cmd [concat | git diff-index --cached $flags]
6862 if {[llength $ids] > 1} {
6863 # comparing index with specific revision
6865 lappend cmd -R [lindex $ids 1]
6867 lappend cmd [lindex $ids 0]
6870 # comparing index with HEAD
6874 set cmd [concat | git diff-tree -r $flags $ids]
6879 proc gettreediffs {ids} {
6880 global treediff treepending
6882 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6884 set treepending $ids
6886 fconfigure $gdtf -blocking 0 -encoding binary
6887 filerun $gdtf [list gettreediffline $gdtf $ids]
6890 proc gettreediffline {gdtf ids} {
6891 global treediff treediffs treepending diffids diffmergeid
6892 global cmitmode vfilelimit curview limitdiffs perfile_attrs
6897 if {$perfile_attrs} {
6898 # cache_gitattr is slow, and even slower on win32 where we
6899 # have to invoke it for only about 30 paths at a time
6901 if {[tk windowingsystem] == "win32"} {
6905 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
6906 set i [string first "\t" $line]
6908 set file [string range $line [expr {$i+1}] end]
6909 if {[string index $file 0] eq "\""} {
6910 set file [lindex $file 0]
6912 set file [encoding convertfrom $file]
6913 lappend treediff $file
6914 lappend sublist $file
6917 if {$perfile_attrs} {
6918 cache_gitattr encoding $sublist
6921 return [expr {$nr >= $max? 2: 1}]
6924 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6926 foreach f $treediff {
6927 if {[path_filter $vfilelimit($curview) $f]} {
6931 set treediffs($ids) $flist
6933 set treediffs($ids) $treediff
6936 if {$cmitmode eq "tree"} {
6938 } elseif {$ids != $diffids} {
6939 if {![info exists diffmergeid]} {
6940 gettreediffs $diffids
6948 # empty string or positive integer
6949 proc diffcontextvalidate {v} {
6950 return [regexp {^(|[1-9][0-9]*)$} $v]
6953 proc diffcontextchange {n1 n2 op} {
6954 global diffcontextstring diffcontext
6956 if {[string is integer -strict $diffcontextstring]} {
6957 if {$diffcontextstring > 0} {
6958 set diffcontext $diffcontextstring
6964 proc changeignorespace {} {
6968 proc getblobdiffs {ids} {
6969 global blobdifffd diffids env
6970 global diffinhdr treediffs
6973 global limitdiffs vfilelimit curview
6974 global diffencoding targetline
6976 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
6980 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6981 set cmd [concat $cmd -- $vfilelimit($curview)]
6983 if {[catch {set bdf [open $cmd r]} err]} {
6984 puts "error getting diffs: $err"
6989 set diffencoding [get_path_encoding {}]
6990 fconfigure $bdf -blocking 0 -encoding binary
6991 set blobdifffd($ids) $bdf
6992 filerun $bdf [list getblobdiffline $bdf $diffids]
6995 proc setinlist {var i val} {
6998 while {[llength [set $var]] < $i} {
7001 if {[llength [set $var]] == $i} {
7008 proc makediffhdr {fname ids} {
7009 global ctext curdiffstart treediffs
7010 global ctext_file_names jump_to_here targetline diffline
7012 set i [lsearch -exact $treediffs($ids) $fname]
7014 setinlist difffilestart $i $curdiffstart
7016 set ctext_file_names [lreplace $ctext_file_names end end $fname]
7017 set l [expr {(78 - [string length $fname]) / 2}]
7018 set pad [string range "----------------------------------------" 1 $l]
7019 $ctext insert $curdiffstart "$pad $fname $pad" filesep
7021 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7022 set targetline [lindex $jump_to_here 1]
7027 proc getblobdiffline {bdf ids} {
7028 global diffids blobdifffd ctext curdiffstart
7029 global diffnexthead diffnextnote difffilestart
7030 global ctext_file_names ctext_file_lines
7031 global diffinhdr treediffs
7032 global diffencoding jump_to_here targetline diffline
7035 $ctext conf -state normal
7036 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7037 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7041 if {![string compare -length 11 "diff --git " $line]} {
7042 # trim off "diff --git "
7043 set line [string range $line 11 end]
7045 # start of a new file
7046 $ctext insert end "\n"
7047 set curdiffstart [$ctext index "end - 1c"]
7048 lappend ctext_file_names ""
7049 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7050 $ctext insert end "\n" filesep
7051 # If the name hasn't changed the length will be odd,
7052 # the middle char will be a space, and the two bits either
7053 # side will be a/name and b/name, or "a/name" and "b/name".
7054 # If the name has changed we'll get "rename from" and
7055 # "rename to" or "copy from" and "copy to" lines following this,
7056 # and we'll use them to get the filenames.
7057 # This complexity is necessary because spaces in the filename(s)
7058 # don't get escaped.
7059 set l [string length $line]
7060 set i [expr {$l / 2}]
7061 if {!(($l & 1) && [string index $line $i] eq " " &&
7062 [string range $line 2 [expr {$i - 1}]] eq \
7063 [string range $line [expr {$i + 3}] end])} {
7066 # unescape if quoted and chop off the a/ from the front
7067 if {[string index $line 0] eq "\""} {
7068 set fname [string range [lindex $line 0] 2 end]
7070 set fname [string range $line 2 [expr {$i - 1}]]
7072 set fname [encoding convertfrom $fname]
7073 set diffencoding [get_path_encoding $fname]
7074 makediffhdr $fname $ids
7076 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
7077 $line match f1l f1c f2l f2c rest]} {
7078 set line [encoding convertfrom $diffencoding $line]
7079 $ctext insert end "$line\n" hunksep
7083 } elseif {$diffinhdr} {
7084 if {![string compare -length 12 "rename from " $line]} {
7085 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7086 if {[string index $fname 0] eq "\""} {
7087 set fname [lindex $fname 0]
7089 set fname [encoding convertfrom $fname]
7090 set i [lsearch -exact $treediffs($ids) $fname]
7092 setinlist difffilestart $i $curdiffstart
7094 } elseif {![string compare -length 10 $line "rename to "] ||
7095 ![string compare -length 8 $line "copy to "]} {
7096 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7097 if {[string index $fname 0] eq "\""} {
7098 set fname [lindex $fname 0]
7100 set fname [encoding convertfrom $fname]
7101 set diffencoding [get_path_encoding $fname]
7102 makediffhdr $fname $ids
7103 } elseif {[string compare -length 3 $line "---"] == 0} {
7106 } elseif {[string compare -length 3 $line "+++"] == 0} {
7110 $ctext insert end "$line\n" filesep
7113 set line [encoding convertfrom $diffencoding $line]
7114 set x [string range $line 0 0]
7115 set here [$ctext index "end - 1 chars"]
7116 if {$x == "-" || $x == "+"} {
7117 set tag [expr {$x == "+"}]
7118 $ctext insert end "$line\n" d$tag
7119 } elseif {$x == " "} {
7120 $ctext insert end "$line\n"
7122 # "\ No newline at end of file",
7123 # or something else we don't recognize
7124 $ctext insert end "$line\n" hunksep
7126 if {$targetline ne {} && ($x eq " " || $x eq "+")} {
7127 if {$diffline == $targetline} {
7128 mark_ctext_line [lindex [split $here .] 0]
7136 $ctext conf -state disabled
7141 return [expr {$nr >= 1000? 2: 1}]
7144 proc changediffdisp {} {
7145 global ctext diffelide
7147 $ctext tag conf d0 -elide [lindex $diffelide 0]
7148 $ctext tag conf d1 -elide [lindex $diffelide 1]
7151 proc highlightfile {loc cline} {
7152 global ctext cflist cflist_top
7155 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7156 $cflist tag add highlight $cline.0 "$cline.0 lineend"
7157 $cflist see $cline.0
7158 set cflist_top $cline
7162 global difffilestart ctext cmitmode
7164 if {$cmitmode eq "tree"} return
7167 set here [$ctext index @0,0]
7168 foreach loc $difffilestart {
7169 if {[$ctext compare $loc >= $here]} {
7170 highlightfile $prev $prevline
7176 highlightfile $prev $prevline
7180 global difffilestart ctext cmitmode
7182 if {$cmitmode eq "tree"} return
7183 set here [$ctext index @0,0]
7185 foreach loc $difffilestart {
7187 if {[$ctext compare $loc > $here]} {
7188 highlightfile $loc $line
7194 proc clear_ctext {{first 1.0}} {
7195 global ctext smarktop smarkbot
7196 global ctext_file_names ctext_file_lines
7199 set l [lindex [split $first .] 0]
7200 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
7203 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
7206 $ctext delete $first end
7207 if {$first eq "1.0"} {
7208 catch {unset pendinglinks}
7210 set ctext_file_names {}
7211 set ctext_file_lines {}
7214 proc settabs {{firstab {}}} {
7215 global firsttabstop tabstop ctext have_tk85
7217 if {$firstab ne {} && $have_tk85} {
7218 set firsttabstop $firstab
7220 set w [font measure textfont "0"]
7221 if {$firsttabstop != 0} {
7222 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
7223 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
7224 } elseif {$have_tk85 || $tabstop != 8} {
7225 $ctext conf -tabs [expr {$tabstop * $w}]
7227 $ctext conf -tabs {}
7231 proc incrsearch {name ix op} {
7232 global ctext searchstring searchdirn
7234 $ctext tag remove found 1.0 end
7235 if {[catch {$ctext index anchor}]} {
7236 # no anchor set, use start of selection, or of visible area
7237 set sel [$ctext tag ranges sel]
7239 $ctext mark set anchor [lindex $sel 0]
7240 } elseif {$searchdirn eq "-forwards"} {
7241 $ctext mark set anchor @0,0
7243 $ctext mark set anchor @0,[winfo height $ctext]
7246 if {$searchstring ne {}} {
7247 set here [$ctext search $searchdirn -- $searchstring anchor]
7256 global sstring ctext searchstring searchdirn
7259 $sstring icursor end
7260 set searchdirn -forwards
7261 if {$searchstring ne {}} {
7262 set sel [$ctext tag ranges sel]
7264 set start "[lindex $sel 0] + 1c"
7265 } elseif {[catch {set start [$ctext index anchor]}]} {
7268 set match [$ctext search -count mlen -- $searchstring $start]
7269 $ctext tag remove sel 1.0 end
7275 set mend "$match + $mlen c"
7276 $ctext tag add sel $match $mend
7277 $ctext mark unset anchor
7281 proc dosearchback {} {
7282 global sstring ctext searchstring searchdirn
7285 $sstring icursor end
7286 set searchdirn -backwards
7287 if {$searchstring ne {}} {
7288 set sel [$ctext tag ranges sel]
7290 set start [lindex $sel 0]
7291 } elseif {[catch {set start [$ctext index anchor]}]} {
7292 set start @0,[winfo height $ctext]
7294 set match [$ctext search -backwards -count ml -- $searchstring $start]
7295 $ctext tag remove sel 1.0 end
7301 set mend "$match + $ml c"
7302 $ctext tag add sel $match $mend
7303 $ctext mark unset anchor
7307 proc searchmark {first last} {
7308 global ctext searchstring
7312 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
7313 if {$match eq {}} break
7314 set mend "$match + $mlen c"
7315 $ctext tag add found $match $mend
7319 proc searchmarkvisible {doall} {
7320 global ctext smarktop smarkbot
7322 set topline [lindex [split [$ctext index @0,0] .] 0]
7323 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
7324 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
7325 # no overlap with previous
7326 searchmark $topline $botline
7327 set smarktop $topline
7328 set smarkbot $botline
7330 if {$topline < $smarktop} {
7331 searchmark $topline [expr {$smarktop-1}]
7332 set smarktop $topline
7334 if {$botline > $smarkbot} {
7335 searchmark [expr {$smarkbot+1}] $botline
7336 set smarkbot $botline
7341 proc scrolltext {f0 f1} {
7344 .bleft.bottom.sb set $f0 $f1
7345 if {$searchstring ne {}} {
7351 global linespc charspc canvx0 canvy0
7352 global xspc1 xspc2 lthickness
7354 set linespc [font metrics mainfont -linespace]
7355 set charspc [font measure mainfont "m"]
7356 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
7357 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
7358 set lthickness [expr {int($linespc / 9) + 1}]
7359 set xspc1(0) $linespc
7367 set ymax [lindex [$canv cget -scrollregion] 3]
7368 if {$ymax eq {} || $ymax == 0} return
7369 set span [$canv yview]
7372 allcanvs yview moveto [lindex $span 0]
7374 if {$selectedline ne {}} {
7375 selectline $selectedline 0
7376 allcanvs yview moveto [lindex $span 0]
7380 proc parsefont {f n} {
7383 set fontattr($f,family) [lindex $n 0]
7385 if {$s eq {} || $s == 0} {
7388 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
7390 set fontattr($f,size) $s
7391 set fontattr($f,weight) normal
7392 set fontattr($f,slant) roman
7393 foreach style [lrange $n 2 end] {
7396 "bold" {set fontattr($f,weight) $style}
7398 "italic" {set fontattr($f,slant) $style}
7403 proc fontflags {f {isbold 0}} {
7406 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
7407 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
7408 -slant $fontattr($f,slant)]
7414 set n [list $fontattr($f,family) $fontattr($f,size)]
7415 if {$fontattr($f,weight) eq "bold"} {
7418 if {$fontattr($f,slant) eq "italic"} {
7424 proc incrfont {inc} {
7425 global mainfont textfont ctext canv cflist showrefstop
7426 global stopped entries fontattr
7429 set s $fontattr(mainfont,size)
7434 set fontattr(mainfont,size) $s
7435 font config mainfont -size $s
7436 font config mainfontbold -size $s
7437 set mainfont [fontname mainfont]
7438 set s $fontattr(textfont,size)
7443 set fontattr(textfont,size) $s
7444 font config textfont -size $s
7445 font config textfontbold -size $s
7446 set textfont [fontname textfont]
7453 global sha1entry sha1string
7454 if {[string length $sha1string] == 40} {
7455 $sha1entry delete 0 end
7459 proc sha1change {n1 n2 op} {
7460 global sha1string currentid sha1but
7461 if {$sha1string == {}
7462 || ([info exists currentid] && $sha1string == $currentid)} {
7467 if {[$sha1but cget -state] == $state} return
7468 if {$state == "normal"} {
7469 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
7471 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
7475 proc gotocommit {} {
7476 global sha1string tagids headids curview varcid
7478 if {$sha1string == {}
7479 || ([info exists currentid] && $sha1string == $currentid)} return
7480 if {[info exists tagids($sha1string)]} {
7481 set id $tagids($sha1string)
7482 } elseif {[info exists headids($sha1string)]} {
7483 set id $headids($sha1string)
7485 set id [string tolower $sha1string]
7486 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7487 set matches [longid $id]
7488 if {$matches ne {}} {
7489 if {[llength $matches] > 1} {
7490 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7493 set id [lindex $matches 0]
7497 if {[commitinview $id $curview]} {
7498 selectline [rowofcommit $id] 1
7501 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7502 set msg [mc "SHA1 id %s is not known" $sha1string]
7504 set msg [mc "Tag/Head %s is not known" $sha1string]
7509 proc lineenter {x y id} {
7510 global hoverx hovery hoverid hovertimer
7511 global commitinfo canv
7513 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7517 if {[info exists hovertimer]} {
7518 after cancel $hovertimer
7520 set hovertimer [after 500 linehover]
7524 proc linemotion {x y id} {
7525 global hoverx hovery hoverid hovertimer
7527 if {[info exists hoverid] && $id == $hoverid} {
7530 if {[info exists hovertimer]} {
7531 after cancel $hovertimer
7533 set hovertimer [after 500 linehover]
7537 proc lineleave {id} {
7538 global hoverid hovertimer canv
7540 if {[info exists hoverid] && $id == $hoverid} {
7542 if {[info exists hovertimer]} {
7543 after cancel $hovertimer
7551 global hoverx hovery hoverid hovertimer
7552 global canv linespc lthickness
7555 set text [lindex $commitinfo($hoverid) 0]
7556 set ymax [lindex [$canv cget -scrollregion] 3]
7557 if {$ymax == {}} return
7558 set yfrac [lindex [$canv yview] 0]
7559 set x [expr {$hoverx + 2 * $linespc}]
7560 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7561 set x0 [expr {$x - 2 * $lthickness}]
7562 set y0 [expr {$y - 2 * $lthickness}]
7563 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7564 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7565 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7566 -fill \#ffff80 -outline black -width 1 -tags hover]
7568 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7573 proc clickisonarrow {id y} {
7576 set ranges [rowranges $id]
7577 set thresh [expr {2 * $lthickness + 6}]
7578 set n [expr {[llength $ranges] - 1}]
7579 for {set i 1} {$i < $n} {incr i} {
7580 set row [lindex $ranges $i]
7581 if {abs([yc $row] - $y) < $thresh} {
7588 proc arrowjump {id n y} {
7591 # 1 <-> 2, 3 <-> 4, etc...
7592 set n [expr {(($n - 1) ^ 1) + 1}]
7593 set row [lindex [rowranges $id] $n]
7595 set ymax [lindex [$canv cget -scrollregion] 3]
7596 if {$ymax eq {} || $ymax <= 0} return
7597 set view [$canv yview]
7598 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7599 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7603 allcanvs yview moveto $yfrac
7606 proc lineclick {x y id isnew} {
7607 global ctext commitinfo children canv thickerline curview
7609 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7614 # draw this line thicker than normal
7618 set ymax [lindex [$canv cget -scrollregion] 3]
7619 if {$ymax eq {}} return
7620 set yfrac [lindex [$canv yview] 0]
7621 set y [expr {$y + $yfrac * $ymax}]
7623 set dirn [clickisonarrow $id $y]
7625 arrowjump $id $dirn $y
7630 addtohistory [list lineclick $x $y $id 0]
7632 # fill the details pane with info about this line
7633 $ctext conf -state normal
7636 $ctext insert end "[mc "Parent"]:\t"
7637 $ctext insert end $id link0
7639 set info $commitinfo($id)
7640 $ctext insert end "\n\t[lindex $info 0]\n"
7641 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7642 set date [formatdate [lindex $info 2]]
7643 $ctext insert end "\t[mc "Date"]:\t$date\n"
7644 set kids $children($curview,$id)
7646 $ctext insert end "\n[mc "Children"]:"
7648 foreach child $kids {
7650 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7651 set info $commitinfo($child)
7652 $ctext insert end "\n\t"
7653 $ctext insert end $child link$i
7654 setlink $child link$i
7655 $ctext insert end "\n\t[lindex $info 0]"
7656 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7657 set date [formatdate [lindex $info 2]]
7658 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7661 $ctext conf -state disabled
7665 proc normalline {} {
7667 if {[info exists thickerline]} {
7676 if {[commitinview $id $curview]} {
7677 selectline [rowofcommit $id] 1
7683 if {![info exists startmstime]} {
7684 set startmstime [clock clicks -milliseconds]
7686 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7689 proc rowmenu {x y id} {
7690 global rowctxmenu selectedline rowmenuid curview
7691 global nullid nullid2 fakerowmenu mainhead
7695 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7700 if {$id ne $nullid && $id ne $nullid2} {
7701 set menu $rowctxmenu
7702 if {$mainhead ne {}} {
7703 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7705 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7708 set menu $fakerowmenu
7710 $menu entryconfigure [mca "Diff this -> selected"] -state $state
7711 $menu entryconfigure [mca "Diff selected -> this"] -state $state
7712 $menu entryconfigure [mca "Make patch"] -state $state
7713 tk_popup $menu $x $y
7716 proc diffvssel {dirn} {
7717 global rowmenuid selectedline
7719 if {$selectedline eq {}} return
7721 set oldid [commitonrow $selectedline]
7722 set newid $rowmenuid
7724 set oldid $rowmenuid
7725 set newid [commitonrow $selectedline]
7727 addtohistory [list doseldiff $oldid $newid]
7728 doseldiff $oldid $newid
7731 proc doseldiff {oldid newid} {
7735 $ctext conf -state normal
7737 init_flist [mc "Top"]
7738 $ctext insert end "[mc "From"] "
7739 $ctext insert end $oldid link0
7740 setlink $oldid link0
7741 $ctext insert end "\n "
7742 $ctext insert end [lindex $commitinfo($oldid) 0]
7743 $ctext insert end "\n\n[mc "To"] "
7744 $ctext insert end $newid link1
7745 setlink $newid link1
7746 $ctext insert end "\n "
7747 $ctext insert end [lindex $commitinfo($newid) 0]
7748 $ctext insert end "\n"
7749 $ctext conf -state disabled
7750 $ctext tag remove found 1.0 end
7751 startdiff [list $oldid $newid]
7755 global rowmenuid currentid commitinfo patchtop patchnum
7757 if {![info exists currentid]} return
7758 set oldid $currentid
7759 set oldhead [lindex $commitinfo($oldid) 0]
7760 set newid $rowmenuid
7761 set newhead [lindex $commitinfo($newid) 0]
7764 catch {destroy $top}
7766 label $top.title -text [mc "Generate patch"]
7767 grid $top.title - -pady 10
7768 label $top.from -text [mc "From:"]
7769 entry $top.fromsha1 -width 40 -relief flat
7770 $top.fromsha1 insert 0 $oldid
7771 $top.fromsha1 conf -state readonly
7772 grid $top.from $top.fromsha1 -sticky w
7773 entry $top.fromhead -width 60 -relief flat
7774 $top.fromhead insert 0 $oldhead
7775 $top.fromhead conf -state readonly
7776 grid x $top.fromhead -sticky w
7777 label $top.to -text [mc "To:"]
7778 entry $top.tosha1 -width 40 -relief flat
7779 $top.tosha1 insert 0 $newid
7780 $top.tosha1 conf -state readonly
7781 grid $top.to $top.tosha1 -sticky w
7782 entry $top.tohead -width 60 -relief flat
7783 $top.tohead insert 0 $newhead
7784 $top.tohead conf -state readonly
7785 grid x $top.tohead -sticky w
7786 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7787 grid $top.rev x -pady 10
7788 label $top.flab -text [mc "Output file:"]
7789 entry $top.fname -width 60
7790 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7792 grid $top.flab $top.fname -sticky w
7794 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7795 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7796 grid $top.buts.gen $top.buts.can
7797 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7798 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7799 grid $top.buts - -pady 10 -sticky ew
7803 proc mkpatchrev {} {
7806 set oldid [$patchtop.fromsha1 get]
7807 set oldhead [$patchtop.fromhead get]
7808 set newid [$patchtop.tosha1 get]
7809 set newhead [$patchtop.tohead get]
7810 foreach e [list fromsha1 fromhead tosha1 tohead] \
7811 v [list $newid $newhead $oldid $oldhead] {
7812 $patchtop.$e conf -state normal
7813 $patchtop.$e delete 0 end
7814 $patchtop.$e insert 0 $v
7815 $patchtop.$e conf -state readonly
7820 global patchtop nullid nullid2
7822 set oldid [$patchtop.fromsha1 get]
7823 set newid [$patchtop.tosha1 get]
7824 set fname [$patchtop.fname get]
7825 set cmd [diffcmd [list $oldid $newid] -p]
7826 # trim off the initial "|"
7827 set cmd [lrange $cmd 1 end]
7828 lappend cmd >$fname &
7829 if {[catch {eval exec $cmd} err]} {
7830 error_popup "[mc "Error creating patch:"] $err"
7832 catch {destroy $patchtop}
7836 proc mkpatchcan {} {
7839 catch {destroy $patchtop}
7844 global rowmenuid mktagtop commitinfo
7848 catch {destroy $top}
7850 label $top.title -text [mc "Create tag"]
7851 grid $top.title - -pady 10
7852 label $top.id -text [mc "ID:"]
7853 entry $top.sha1 -width 40 -relief flat
7854 $top.sha1 insert 0 $rowmenuid
7855 $top.sha1 conf -state readonly
7856 grid $top.id $top.sha1 -sticky w
7857 entry $top.head -width 60 -relief flat
7858 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7859 $top.head conf -state readonly
7860 grid x $top.head -sticky w
7861 label $top.tlab -text [mc "Tag name:"]
7862 entry $top.tag -width 60
7863 grid $top.tlab $top.tag -sticky w
7865 button $top.buts.gen -text [mc "Create"] -command mktaggo
7866 button $top.buts.can -text [mc "Cancel"] -command mktagcan
7867 grid $top.buts.gen $top.buts.can
7868 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7869 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7870 grid $top.buts - -pady 10 -sticky ew
7875 global mktagtop env tagids idtags
7877 set id [$mktagtop.sha1 get]
7878 set tag [$mktagtop.tag get]
7880 error_popup [mc "No tag name specified"]
7883 if {[info exists tagids($tag)]} {
7884 error_popup [mc "Tag \"%s\" already exists" $tag]
7888 exec git tag $tag $id
7890 error_popup "[mc "Error creating tag:"] $err"
7894 set tagids($tag) $id
7895 lappend idtags($id) $tag
7902 proc redrawtags {id} {
7903 global canv linehtag idpos currentid curview cmitlisted
7904 global canvxmax iddrawn circleitem mainheadid circlecolors
7906 if {![commitinview $id $curview]} return
7907 if {![info exists iddrawn($id)]} return
7908 set row [rowofcommit $id]
7909 if {$id eq $mainheadid} {
7912 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
7914 $canv itemconf $circleitem($row) -fill $ofill
7915 $canv delete tag.$id
7916 set xt [eval drawtags $id $idpos($id)]
7917 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
7918 set text [$canv itemcget $linehtag($row) -text]
7919 set font [$canv itemcget $linehtag($row) -font]
7920 set xr [expr {$xt + [font measure $font $text]}]
7921 if {$xr > $canvxmax} {
7925 if {[info exists currentid] && $currentid == $id} {
7933 catch {destroy $mktagtop}
7942 proc writecommit {} {
7943 global rowmenuid wrcomtop commitinfo wrcomcmd
7945 set top .writecommit
7947 catch {destroy $top}
7949 label $top.title -text [mc "Write commit to file"]
7950 grid $top.title - -pady 10
7951 label $top.id -text [mc "ID:"]
7952 entry $top.sha1 -width 40 -relief flat
7953 $top.sha1 insert 0 $rowmenuid
7954 $top.sha1 conf -state readonly
7955 grid $top.id $top.sha1 -sticky w
7956 entry $top.head -width 60 -relief flat
7957 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7958 $top.head conf -state readonly
7959 grid x $top.head -sticky w
7960 label $top.clab -text [mc "Command:"]
7961 entry $top.cmd -width 60 -textvariable wrcomcmd
7962 grid $top.clab $top.cmd -sticky w -pady 10
7963 label $top.flab -text [mc "Output file:"]
7964 entry $top.fname -width 60
7965 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
7966 grid $top.flab $top.fname -sticky w
7968 button $top.buts.gen -text [mc "Write"] -command wrcomgo
7969 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
7970 grid $top.buts.gen $top.buts.can
7971 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7972 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7973 grid $top.buts - -pady 10 -sticky ew
7980 set id [$wrcomtop.sha1 get]
7981 set cmd "echo $id | [$wrcomtop.cmd get]"
7982 set fname [$wrcomtop.fname get]
7983 if {[catch {exec sh -c $cmd >$fname &} err]} {
7984 error_popup "[mc "Error writing commit:"] $err"
7986 catch {destroy $wrcomtop}
7993 catch {destroy $wrcomtop}
7998 global rowmenuid mkbrtop
8001 catch {destroy $top}
8003 label $top.title -text [mc "Create new branch"]
8004 grid $top.title - -pady 10
8005 label $top.id -text [mc "ID:"]
8006 entry $top.sha1 -width 40 -relief flat
8007 $top.sha1 insert 0 $rowmenuid
8008 $top.sha1 conf -state readonly
8009 grid $top.id $top.sha1 -sticky w
8010 label $top.nlab -text [mc "Name:"]
8011 entry $top.name -width 40
8012 bind $top.name <Key-Return> "[list mkbrgo $top]"
8013 grid $top.nlab $top.name -sticky w
8015 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
8016 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
8017 grid $top.buts.go $top.buts.can
8018 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8019 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8020 grid $top.buts - -pady 10 -sticky ew
8025 global headids idheads
8027 set name [$top.name get]
8028 set id [$top.sha1 get]
8032 error_popup [mc "Please specify a name for the new branch"]
8035 if {[info exists headids($name)]} {
8036 if {![confirm_popup [mc \
8037 "Branch '%s' already exists. Overwrite?" $name]]} {
8040 set old_id $headids($name)
8043 catch {destroy $top}
8044 lappend cmdargs $name $id
8048 eval exec git branch $cmdargs
8054 if {$old_id ne {}} {
8060 set headids($name) $id
8061 lappend idheads($id) $name
8070 proc cherrypick {} {
8071 global rowmenuid curview
8072 global mainhead mainheadid
8074 set oldhead [exec git rev-parse HEAD]
8075 set dheads [descheads $rowmenuid]
8076 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
8077 set ok [confirm_popup [mc "Commit %s is already\
8078 included in branch %s -- really re-apply it?" \
8079 [string range $rowmenuid 0 7] $mainhead]]
8082 nowbusy cherrypick [mc "Cherry-picking"]
8084 # Unfortunately git-cherry-pick writes stuff to stderr even when
8085 # no error occurs, and exec takes that as an indication of error...
8086 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
8091 set newhead [exec git rev-parse HEAD]
8092 if {$newhead eq $oldhead} {
8094 error_popup [mc "No changes committed"]
8097 addnewchild $newhead $oldhead
8098 if {[commitinview $oldhead $curview]} {
8099 insertrow $newhead $oldhead $curview
8100 if {$mainhead ne {}} {
8101 movehead $newhead $mainhead
8102 movedhead $newhead $mainhead
8104 set mainheadid $newhead
8113 global mainhead rowmenuid confirm_ok resettype
8116 set w ".confirmreset"
8119 wm title $w [mc "Confirm reset"]
8120 message $w.m -text \
8121 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
8122 -justify center -aspect 1000
8123 pack $w.m -side top -fill x -padx 20 -pady 20
8124 frame $w.f -relief sunken -border 2
8125 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
8126 grid $w.f.rt -sticky w
8128 radiobutton $w.f.soft -value soft -variable resettype -justify left \
8129 -text [mc "Soft: Leave working tree and index untouched"]
8130 grid $w.f.soft -sticky w
8131 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
8132 -text [mc "Mixed: Leave working tree untouched, reset index"]
8133 grid $w.f.mixed -sticky w
8134 radiobutton $w.f.hard -value hard -variable resettype -justify left \
8135 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
8136 grid $w.f.hard -sticky w
8137 pack $w.f -side top -fill x
8138 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
8139 pack $w.ok -side left -fill x -padx 20 -pady 20
8140 button $w.cancel -text [mc Cancel] -command "destroy $w"
8141 pack $w.cancel -side right -fill x -padx 20 -pady 20
8142 bind $w <Visibility> "grab $w; focus $w"
8144 if {!$confirm_ok} return
8145 if {[catch {set fd [open \
8146 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
8150 filerun $fd [list readresetstat $fd]
8151 nowbusy reset [mc "Resetting"]
8156 proc readresetstat {fd} {
8157 global mainhead mainheadid showlocalchanges rprogcoord
8159 if {[gets $fd line] >= 0} {
8160 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8161 set rprogcoord [expr {1.0 * $m / $n}]
8169 if {[catch {close $fd} err]} {
8172 set oldhead $mainheadid
8173 set newhead [exec git rev-parse HEAD]
8174 if {$newhead ne $oldhead} {
8175 movehead $newhead $mainhead
8176 movedhead $newhead $mainhead
8177 set mainheadid $newhead
8181 if {$showlocalchanges} {
8187 # context menu for a head
8188 proc headmenu {x y id head} {
8189 global headmenuid headmenuhead headctxmenu mainhead
8193 set headmenuhead $head
8195 if {$head eq $mainhead} {
8198 $headctxmenu entryconfigure 0 -state $state
8199 $headctxmenu entryconfigure 1 -state $state
8200 tk_popup $headctxmenu $x $y
8204 global headmenuid headmenuhead headids
8205 global showlocalchanges mainheadid
8207 # check the tree is clean first??
8208 nowbusy checkout [mc "Checking out"]
8212 set fd [open [list | git checkout $headmenuhead 2>@1] r]
8216 if {$showlocalchanges} {
8220 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
8224 proc readcheckoutstat {fd newhead newheadid} {
8225 global mainhead mainheadid headids showlocalchanges progresscoords
8227 if {[gets $fd line] >= 0} {
8228 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8229 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
8234 set progresscoords {0 0}
8237 if {[catch {close $fd} err]} {
8240 set oldmainid $mainheadid
8241 set mainhead $newhead
8242 set mainheadid $newheadid
8243 redrawtags $oldmainid
8244 redrawtags $newheadid
8246 if {$showlocalchanges} {
8252 global headmenuid headmenuhead mainhead
8255 set head $headmenuhead
8257 # this check shouldn't be needed any more...
8258 if {$head eq $mainhead} {
8259 error_popup [mc "Cannot delete the currently checked-out branch"]
8262 set dheads [descheads $id]
8263 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
8264 # the stuff on this branch isn't on any other branch
8265 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
8266 branch.\nReally delete branch %s?" $head $head]]} return
8270 if {[catch {exec git branch -D $head} err]} {
8275 removehead $id $head
8276 removedhead $id $head
8283 # Display a list of tags and heads
8285 global showrefstop bgcolor fgcolor selectbgcolor
8286 global bglist fglist reflistfilter reflist maincursor
8289 set showrefstop $top
8290 if {[winfo exists $top]} {
8296 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
8297 text $top.list -background $bgcolor -foreground $fgcolor \
8298 -selectbackground $selectbgcolor -font mainfont \
8299 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
8300 -width 30 -height 20 -cursor $maincursor \
8301 -spacing1 1 -spacing3 1 -state disabled
8302 $top.list tag configure highlight -background $selectbgcolor
8303 lappend bglist $top.list
8304 lappend fglist $top.list
8305 scrollbar $top.ysb -command "$top.list yview" -orient vertical
8306 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
8307 grid $top.list $top.ysb -sticky nsew
8308 grid $top.xsb x -sticky ew
8310 label $top.f.l -text "[mc "Filter"]: "
8311 entry $top.f.e -width 20 -textvariable reflistfilter
8312 set reflistfilter "*"
8313 trace add variable reflistfilter write reflistfilter_change
8314 pack $top.f.e -side right -fill x -expand 1
8315 pack $top.f.l -side left
8316 grid $top.f - -sticky ew -pady 2
8317 button $top.close -command [list destroy $top] -text [mc "Close"]
8319 grid columnconfigure $top 0 -weight 1
8320 grid rowconfigure $top 0 -weight 1
8321 bind $top.list <1> {break}
8322 bind $top.list <B1-Motion> {break}
8323 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
8328 proc sel_reflist {w x y} {
8329 global showrefstop reflist headids tagids otherrefids
8331 if {![winfo exists $showrefstop]} return
8332 set l [lindex [split [$w index "@$x,$y"] "."] 0]
8333 set ref [lindex $reflist [expr {$l-1}]]
8334 set n [lindex $ref 0]
8335 switch -- [lindex $ref 1] {
8336 "H" {selbyid $headids($n)}
8337 "T" {selbyid $tagids($n)}
8338 "o" {selbyid $otherrefids($n)}
8340 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
8343 proc unsel_reflist {} {
8346 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8347 $showrefstop.list tag remove highlight 0.0 end
8350 proc reflistfilter_change {n1 n2 op} {
8351 global reflistfilter
8353 after cancel refill_reflist
8354 after 200 refill_reflist
8357 proc refill_reflist {} {
8358 global reflist reflistfilter showrefstop headids tagids otherrefids
8361 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8363 foreach n [array names headids] {
8364 if {[string match $reflistfilter $n]} {
8365 if {[commitinview $headids($n) $curview]} {
8366 lappend refs [list $n H]
8368 interestedin $headids($n) {run refill_reflist}
8372 foreach n [array names tagids] {
8373 if {[string match $reflistfilter $n]} {
8374 if {[commitinview $tagids($n) $curview]} {
8375 lappend refs [list $n T]
8377 interestedin $tagids($n) {run refill_reflist}
8381 foreach n [array names otherrefids] {
8382 if {[string match $reflistfilter $n]} {
8383 if {[commitinview $otherrefids($n) $curview]} {
8384 lappend refs [list $n o]
8386 interestedin $otherrefids($n) {run refill_reflist}
8390 set refs [lsort -index 0 $refs]
8391 if {$refs eq $reflist} return
8393 # Update the contents of $showrefstop.list according to the
8394 # differences between $reflist (old) and $refs (new)
8395 $showrefstop.list conf -state normal
8396 $showrefstop.list insert end "\n"
8399 while {$i < [llength $reflist] || $j < [llength $refs]} {
8400 if {$i < [llength $reflist]} {
8401 if {$j < [llength $refs]} {
8402 set cmp [string compare [lindex $reflist $i 0] \
8403 [lindex $refs $j 0]]
8405 set cmp [string compare [lindex $reflist $i 1] \
8406 [lindex $refs $j 1]]
8416 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
8424 set l [expr {$j + 1}]
8425 $showrefstop.list image create $l.0 -align baseline \
8426 -image reficon-[lindex $refs $j 1] -padx 2
8427 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
8433 # delete last newline
8434 $showrefstop.list delete end-2c end-1c
8435 $showrefstop.list conf -state disabled
8438 # Stuff for finding nearby tags
8439 proc getallcommits {} {
8440 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
8441 global idheads idtags idotherrefs allparents tagobjid
8443 if {![info exists allcommits]} {
8449 set allccache [file join [gitdir] "gitk.cache"]
8451 set f [open $allccache r]
8460 set cmd [list | git rev-list --parents]
8461 set allcupdate [expr {$seeds ne {}}]
8465 set refs [concat [array names idheads] [array names idtags] \
8466 [array names idotherrefs]]
8469 foreach name [array names tagobjid] {
8470 lappend tagobjs $tagobjid($name)
8472 foreach id [lsort -unique $refs] {
8473 if {![info exists allparents($id)] &&
8474 [lsearch -exact $tagobjs $id] < 0} {
8485 set fd [open [concat $cmd $ids] r]
8486 fconfigure $fd -blocking 0
8489 filerun $fd [list getallclines $fd]
8495 # Since most commits have 1 parent and 1 child, we group strings of
8496 # such commits into "arcs" joining branch/merge points (BMPs), which
8497 # are commits that either don't have 1 parent or don't have 1 child.
8499 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8500 # arcout(id) - outgoing arcs for BMP
8501 # arcids(a) - list of IDs on arc including end but not start
8502 # arcstart(a) - BMP ID at start of arc
8503 # arcend(a) - BMP ID at end of arc
8504 # growing(a) - arc a is still growing
8505 # arctags(a) - IDs out of arcids (excluding end) that have tags
8506 # archeads(a) - IDs out of arcids (excluding end) that have heads
8507 # The start of an arc is at the descendent end, so "incoming" means
8508 # coming from descendents, and "outgoing" means going towards ancestors.
8510 proc getallclines {fd} {
8511 global allparents allchildren idtags idheads nextarc
8512 global arcnos arcids arctags arcout arcend arcstart archeads growing
8513 global seeds allcommits cachedarcs allcupdate
8516 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8517 set id [lindex $line 0]
8518 if {[info exists allparents($id)]} {
8523 set olds [lrange $line 1 end]
8524 set allparents($id) $olds
8525 if {![info exists allchildren($id)]} {
8526 set allchildren($id) {}
8531 if {[llength $olds] == 1 && [llength $a] == 1} {
8532 lappend arcids($a) $id
8533 if {[info exists idtags($id)]} {
8534 lappend arctags($a) $id
8536 if {[info exists idheads($id)]} {
8537 lappend archeads($a) $id
8539 if {[info exists allparents($olds)]} {
8540 # seen parent already
8541 if {![info exists arcout($olds)]} {
8544 lappend arcids($a) $olds
8545 set arcend($a) $olds
8548 lappend allchildren($olds) $id
8549 lappend arcnos($olds) $a
8553 foreach a $arcnos($id) {
8554 lappend arcids($a) $id
8561 lappend allchildren($p) $id
8562 set a [incr nextarc]
8563 set arcstart($a) $id
8570 if {[info exists allparents($p)]} {
8571 # seen it already, may need to make a new branch
8572 if {![info exists arcout($p)]} {
8575 lappend arcids($a) $p
8579 lappend arcnos($p) $a
8584 global cached_dheads cached_dtags cached_atags
8585 catch {unset cached_dheads}
8586 catch {unset cached_dtags}
8587 catch {unset cached_atags}
8590 return [expr {$nid >= 1000? 2: 1}]
8594 fconfigure $fd -blocking 1
8597 # got an error reading the list of commits
8598 # if we were updating, try rereading the whole thing again
8604 error_popup "[mc "Error reading commit topology information;\
8605 branch and preceding/following tag information\
8606 will be incomplete."]\n($err)"
8609 if {[incr allcommits -1] == 0} {
8619 proc recalcarc {a} {
8620 global arctags archeads arcids idtags idheads
8624 foreach id [lrange $arcids($a) 0 end-1] {
8625 if {[info exists idtags($id)]} {
8628 if {[info exists idheads($id)]} {
8633 set archeads($a) $ah
8637 global arcnos arcids nextarc arctags archeads idtags idheads
8638 global arcstart arcend arcout allparents growing
8641 if {[llength $a] != 1} {
8642 puts "oops splitarc called but [llength $a] arcs already"
8646 set i [lsearch -exact $arcids($a) $p]
8648 puts "oops splitarc $p not in arc $a"
8651 set na [incr nextarc]
8652 if {[info exists arcend($a)]} {
8653 set arcend($na) $arcend($a)
8655 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8656 set j [lsearch -exact $arcnos($l) $a]
8657 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8659 set tail [lrange $arcids($a) [expr {$i+1}] end]
8660 set arcids($a) [lrange $arcids($a) 0 $i]
8662 set arcstart($na) $p
8664 set arcids($na) $tail
8665 if {[info exists growing($a)]} {
8671 if {[llength $arcnos($id)] == 1} {
8674 set j [lsearch -exact $arcnos($id) $a]
8675 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8679 # reconstruct tags and heads lists
8680 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8685 set archeads($na) {}
8689 # Update things for a new commit added that is a child of one
8690 # existing commit. Used when cherry-picking.
8691 proc addnewchild {id p} {
8692 global allparents allchildren idtags nextarc
8693 global arcnos arcids arctags arcout arcend arcstart archeads growing
8694 global seeds allcommits
8696 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8697 set allparents($id) [list $p]
8698 set allchildren($id) {}
8701 lappend allchildren($p) $id
8702 set a [incr nextarc]
8703 set arcstart($a) $id
8706 set arcids($a) [list $p]
8708 if {![info exists arcout($p)]} {
8711 lappend arcnos($p) $a
8712 set arcout($id) [list $a]
8715 # This implements a cache for the topology information.
8716 # The cache saves, for each arc, the start and end of the arc,
8717 # the ids on the arc, and the outgoing arcs from the end.
8718 proc readcache {f} {
8719 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8720 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8725 if {$lim - $a > 500} {
8726 set lim [expr {$a + 500}]
8730 # finish reading the cache and setting up arctags, etc.
8732 if {$line ne "1"} {error "bad final version"}
8734 foreach id [array names idtags] {
8735 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8736 [llength $allparents($id)] == 1} {
8737 set a [lindex $arcnos($id) 0]
8738 if {$arctags($a) eq {}} {
8743 foreach id [array names idheads] {
8744 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8745 [llength $allparents($id)] == 1} {
8746 set a [lindex $arcnos($id) 0]
8747 if {$archeads($a) eq {}} {
8752 foreach id [lsort -unique $possible_seeds] {
8753 if {$arcnos($id) eq {}} {
8759 while {[incr a] <= $lim} {
8761 if {[llength $line] != 3} {error "bad line"}
8762 set s [lindex $line 0]
8764 lappend arcout($s) $a
8765 if {![info exists arcnos($s)]} {
8766 lappend possible_seeds $s
8769 set e [lindex $line 1]
8774 if {![info exists arcout($e)]} {
8778 set arcids($a) [lindex $line 2]
8779 foreach id $arcids($a) {
8780 lappend allparents($s) $id
8782 lappend arcnos($id) $a
8784 if {![info exists allparents($s)]} {
8785 set allparents($s) {}
8790 set nextarc [expr {$a - 1}]
8803 global nextarc cachedarcs possible_seeds
8807 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
8808 # make sure it's an integer
8809 set cachedarcs [expr {int([lindex $line 1])}]
8810 if {$cachedarcs < 0} {error "bad number of arcs"}
8812 set possible_seeds {}
8820 proc dropcache {err} {
8821 global allcwait nextarc cachedarcs seeds
8823 #puts "dropping cache ($err)"
8824 foreach v {arcnos arcout arcids arcstart arcend growing \
8825 arctags archeads allparents allchildren} {
8836 proc writecache {f} {
8837 global cachearc cachedarcs allccache
8838 global arcstart arcend arcnos arcids arcout
8842 if {$lim - $a > 1000} {
8843 set lim [expr {$a + 1000}]
8846 while {[incr a] <= $lim} {
8847 if {[info exists arcend($a)]} {
8848 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
8850 puts $f [list $arcstart($a) {} $arcids($a)]
8855 catch {file delete $allccache}
8856 #puts "writing cache failed ($err)"
8859 set cachearc [expr {$a - 1}]
8860 if {$a > $cachedarcs} {
8869 global nextarc cachedarcs cachearc allccache
8871 if {$nextarc == $cachedarcs} return
8873 set cachedarcs $nextarc
8875 set f [open $allccache w]
8876 puts $f [list 1 $cachedarcs]
8881 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
8882 # or 0 if neither is true.
8883 proc anc_or_desc {a b} {
8884 global arcout arcstart arcend arcnos cached_isanc
8886 if {$arcnos($a) eq $arcnos($b)} {
8887 # Both are on the same arc(s); either both are the same BMP,
8888 # or if one is not a BMP, the other is also not a BMP or is
8889 # the BMP at end of the arc (and it only has 1 incoming arc).
8890 # Or both can be BMPs with no incoming arcs.
8891 if {$a eq $b || $arcnos($a) eq {}} {
8894 # assert {[llength $arcnos($a)] == 1}
8895 set arc [lindex $arcnos($a) 0]
8896 set i [lsearch -exact $arcids($arc) $a]
8897 set j [lsearch -exact $arcids($arc) $b]
8898 if {$i < 0 || $i > $j} {
8905 if {![info exists arcout($a)]} {
8906 set arc [lindex $arcnos($a) 0]
8907 if {[info exists arcend($arc)]} {
8908 set aend $arcend($arc)
8912 set a $arcstart($arc)
8916 if {![info exists arcout($b)]} {
8917 set arc [lindex $arcnos($b) 0]
8918 if {[info exists arcend($arc)]} {
8919 set bend $arcend($arc)
8923 set b $arcstart($arc)
8933 if {[info exists cached_isanc($a,$bend)]} {
8934 if {$cached_isanc($a,$bend)} {
8938 if {[info exists cached_isanc($b,$aend)]} {
8939 if {$cached_isanc($b,$aend)} {
8942 if {[info exists cached_isanc($a,$bend)]} {
8947 set todo [list $a $b]
8950 for {set i 0} {$i < [llength $todo]} {incr i} {
8951 set x [lindex $todo $i]
8952 if {$anc($x) eq {}} {
8955 foreach arc $arcnos($x) {
8956 set xd $arcstart($arc)
8958 set cached_isanc($a,$bend) 1
8959 set cached_isanc($b,$aend) 0
8961 } elseif {$xd eq $aend} {
8962 set cached_isanc($b,$aend) 1
8963 set cached_isanc($a,$bend) 0
8966 if {![info exists anc($xd)]} {
8967 set anc($xd) $anc($x)
8969 } elseif {$anc($xd) ne $anc($x)} {
8974 set cached_isanc($a,$bend) 0
8975 set cached_isanc($b,$aend) 0
8979 # This identifies whether $desc has an ancestor that is
8980 # a growing tip of the graph and which is not an ancestor of $anc
8981 # and returns 0 if so and 1 if not.
8982 # If we subsequently discover a tag on such a growing tip, and that
8983 # turns out to be a descendent of $anc (which it could, since we
8984 # don't necessarily see children before parents), then $desc
8985 # isn't a good choice to display as a descendent tag of
8986 # $anc (since it is the descendent of another tag which is
8987 # a descendent of $anc). Similarly, $anc isn't a good choice to
8988 # display as a ancestor tag of $desc.
8990 proc is_certain {desc anc} {
8991 global arcnos arcout arcstart arcend growing problems
8994 if {[llength $arcnos($anc)] == 1} {
8995 # tags on the same arc are certain
8996 if {$arcnos($desc) eq $arcnos($anc)} {
8999 if {![info exists arcout($anc)]} {
9000 # if $anc is partway along an arc, use the start of the arc instead
9001 set a [lindex $arcnos($anc) 0]
9002 set anc $arcstart($a)
9005 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
9008 set a [lindex $arcnos($desc) 0]
9014 set anclist [list $x]
9018 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
9019 set x [lindex $anclist $i]
9024 foreach a $arcout($x) {
9025 if {[info exists growing($a)]} {
9026 if {![info exists growanc($x)] && $dl($x)} {
9032 if {[info exists dl($y)]} {
9036 if {![info exists done($y)]} {
9039 if {[info exists growanc($x)]} {
9043 for {set k 0} {$k < [llength $xl]} {incr k} {
9044 set z [lindex $xl $k]
9045 foreach c $arcout($z) {
9046 if {[info exists arcend($c)]} {
9048 if {[info exists dl($v)] && $dl($v)} {
9050 if {![info exists done($v)]} {
9053 if {[info exists growanc($v)]} {
9063 } elseif {$y eq $anc || !$dl($x)} {
9074 foreach x [array names growanc] {
9083 proc validate_arctags {a} {
9084 global arctags idtags
9088 foreach id $arctags($a) {
9090 if {![info exists idtags($id)]} {
9091 set na [lreplace $na $i $i]
9098 proc validate_archeads {a} {
9099 global archeads idheads
9102 set na $archeads($a)
9103 foreach id $archeads($a) {
9105 if {![info exists idheads($id)]} {
9106 set na [lreplace $na $i $i]
9110 set archeads($a) $na
9113 # Return the list of IDs that have tags that are descendents of id,
9114 # ignoring IDs that are descendents of IDs already reported.
9115 proc desctags {id} {
9116 global arcnos arcstart arcids arctags idtags allparents
9117 global growing cached_dtags
9119 if {![info exists allparents($id)]} {
9122 set t1 [clock clicks -milliseconds]
9124 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9125 # part-way along an arc; check that arc first
9126 set a [lindex $arcnos($id) 0]
9127 if {$arctags($a) ne {}} {
9129 set i [lsearch -exact $arcids($a) $id]
9131 foreach t $arctags($a) {
9132 set j [lsearch -exact $arcids($a) $t]
9140 set id $arcstart($a)
9141 if {[info exists idtags($id)]} {
9145 if {[info exists cached_dtags($id)]} {
9146 return $cached_dtags($id)
9153 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9154 set id [lindex $todo $i]
9156 set ta [info exists hastaggedancestor($id)]
9160 # ignore tags on starting node
9161 if {!$ta && $i > 0} {
9162 if {[info exists idtags($id)]} {
9165 } elseif {[info exists cached_dtags($id)]} {
9166 set tagloc($id) $cached_dtags($id)
9170 foreach a $arcnos($id) {
9172 if {!$ta && $arctags($a) ne {}} {
9174 if {$arctags($a) ne {}} {
9175 lappend tagloc($id) [lindex $arctags($a) end]
9178 if {$ta || $arctags($a) ne {}} {
9179 set tomark [list $d]
9180 for {set j 0} {$j < [llength $tomark]} {incr j} {
9181 set dd [lindex $tomark $j]
9182 if {![info exists hastaggedancestor($dd)]} {
9183 if {[info exists done($dd)]} {
9184 foreach b $arcnos($dd) {
9185 lappend tomark $arcstart($b)
9187 if {[info exists tagloc($dd)]} {
9190 } elseif {[info exists queued($dd)]} {
9193 set hastaggedancestor($dd) 1
9197 if {![info exists queued($d)]} {
9200 if {![info exists hastaggedancestor($d)]} {
9207 foreach id [array names tagloc] {
9208 if {![info exists hastaggedancestor($id)]} {
9209 foreach t $tagloc($id) {
9210 if {[lsearch -exact $tags $t] < 0} {
9216 set t2 [clock clicks -milliseconds]
9219 # remove tags that are descendents of other tags
9220 for {set i 0} {$i < [llength $tags]} {incr i} {
9221 set a [lindex $tags $i]
9222 for {set j 0} {$j < $i} {incr j} {
9223 set b [lindex $tags $j]
9224 set r [anc_or_desc $a $b]
9226 set tags [lreplace $tags $j $j]
9229 } elseif {$r == -1} {
9230 set tags [lreplace $tags $i $i]
9237 if {[array names growing] ne {}} {
9238 # graph isn't finished, need to check if any tag could get
9239 # eclipsed by another tag coming later. Simply ignore any
9240 # tags that could later get eclipsed.
9243 if {[is_certain $t $origid]} {
9247 if {$tags eq $ctags} {
9248 set cached_dtags($origid) $tags
9253 set cached_dtags($origid) $tags
9255 set t3 [clock clicks -milliseconds]
9256 if {0 && $t3 - $t1 >= 100} {
9257 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
9258 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9264 global arcnos arcids arcout arcend arctags idtags allparents
9265 global growing cached_atags
9267 if {![info exists allparents($id)]} {
9270 set t1 [clock clicks -milliseconds]
9272 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9273 # part-way along an arc; check that arc first
9274 set a [lindex $arcnos($id) 0]
9275 if {$arctags($a) ne {}} {
9277 set i [lsearch -exact $arcids($a) $id]
9278 foreach t $arctags($a) {
9279 set j [lsearch -exact $arcids($a) $t]
9285 if {![info exists arcend($a)]} {
9289 if {[info exists idtags($id)]} {
9293 if {[info exists cached_atags($id)]} {
9294 return $cached_atags($id)
9302 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9303 set id [lindex $todo $i]
9305 set td [info exists hastaggeddescendent($id)]
9309 # ignore tags on starting node
9310 if {!$td && $i > 0} {
9311 if {[info exists idtags($id)]} {
9314 } elseif {[info exists cached_atags($id)]} {
9315 set tagloc($id) $cached_atags($id)
9319 foreach a $arcout($id) {
9320 if {!$td && $arctags($a) ne {}} {
9322 if {$arctags($a) ne {}} {
9323 lappend tagloc($id) [lindex $arctags($a) 0]
9326 if {![info exists arcend($a)]} continue
9328 if {$td || $arctags($a) ne {}} {
9329 set tomark [list $d]
9330 for {set j 0} {$j < [llength $tomark]} {incr j} {
9331 set dd [lindex $tomark $j]
9332 if {![info exists hastaggeddescendent($dd)]} {
9333 if {[info exists done($dd)]} {
9334 foreach b $arcout($dd) {
9335 if {[info exists arcend($b)]} {
9336 lappend tomark $arcend($b)
9339 if {[info exists tagloc($dd)]} {
9342 } elseif {[info exists queued($dd)]} {
9345 set hastaggeddescendent($dd) 1
9349 if {![info exists queued($d)]} {
9352 if {![info exists hastaggeddescendent($d)]} {
9358 set t2 [clock clicks -milliseconds]
9361 foreach id [array names tagloc] {
9362 if {![info exists hastaggeddescendent($id)]} {
9363 foreach t $tagloc($id) {
9364 if {[lsearch -exact $tags $t] < 0} {
9371 # remove tags that are ancestors of other tags
9372 for {set i 0} {$i < [llength $tags]} {incr i} {
9373 set a [lindex $tags $i]
9374 for {set j 0} {$j < $i} {incr j} {
9375 set b [lindex $tags $j]
9376 set r [anc_or_desc $a $b]
9378 set tags [lreplace $tags $j $j]
9381 } elseif {$r == 1} {
9382 set tags [lreplace $tags $i $i]
9389 if {[array names growing] ne {}} {
9390 # graph isn't finished, need to check if any tag could get
9391 # eclipsed by another tag coming later. Simply ignore any
9392 # tags that could later get eclipsed.
9395 if {[is_certain $origid $t]} {
9399 if {$tags eq $ctags} {
9400 set cached_atags($origid) $tags
9405 set cached_atags($origid) $tags
9407 set t3 [clock clicks -milliseconds]
9408 if {0 && $t3 - $t1 >= 100} {
9409 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
9410 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9415 # Return the list of IDs that have heads that are descendents of id,
9416 # including id itself if it has a head.
9417 proc descheads {id} {
9418 global arcnos arcstart arcids archeads idheads cached_dheads
9421 if {![info exists allparents($id)]} {
9425 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9426 # part-way along an arc; check it first
9427 set a [lindex $arcnos($id) 0]
9428 if {$archeads($a) ne {}} {
9429 validate_archeads $a
9430 set i [lsearch -exact $arcids($a) $id]
9431 foreach t $archeads($a) {
9432 set j [lsearch -exact $arcids($a) $t]
9437 set id $arcstart($a)
9443 for {set i 0} {$i < [llength $todo]} {incr i} {
9444 set id [lindex $todo $i]
9445 if {[info exists cached_dheads($id)]} {
9446 set ret [concat $ret $cached_dheads($id)]
9448 if {[info exists idheads($id)]} {
9451 foreach a $arcnos($id) {
9452 if {$archeads($a) ne {}} {
9453 validate_archeads $a
9454 if {$archeads($a) ne {}} {
9455 set ret [concat $ret $archeads($a)]
9459 if {![info exists seen($d)]} {
9466 set ret [lsort -unique $ret]
9467 set cached_dheads($origid) $ret
9468 return [concat $ret $aret]
9471 proc addedtag {id} {
9472 global arcnos arcout cached_dtags cached_atags
9474 if {![info exists arcnos($id)]} return
9475 if {![info exists arcout($id)]} {
9476 recalcarc [lindex $arcnos($id) 0]
9478 catch {unset cached_dtags}
9479 catch {unset cached_atags}
9482 proc addedhead {hid head} {
9483 global arcnos arcout cached_dheads
9485 if {![info exists arcnos($hid)]} return
9486 if {![info exists arcout($hid)]} {
9487 recalcarc [lindex $arcnos($hid) 0]
9489 catch {unset cached_dheads}
9492 proc removedhead {hid head} {
9493 global cached_dheads
9495 catch {unset cached_dheads}
9498 proc movedhead {hid head} {
9499 global arcnos arcout cached_dheads
9501 if {![info exists arcnos($hid)]} return
9502 if {![info exists arcout($hid)]} {
9503 recalcarc [lindex $arcnos($hid) 0]
9505 catch {unset cached_dheads}
9508 proc changedrefs {} {
9509 global cached_dheads cached_dtags cached_atags
9510 global arctags archeads arcnos arcout idheads idtags
9512 foreach id [concat [array names idheads] [array names idtags]] {
9513 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9514 set a [lindex $arcnos($id) 0]
9515 if {![info exists donearc($a)]} {
9521 catch {unset cached_dtags}
9522 catch {unset cached_atags}
9523 catch {unset cached_dheads}
9526 proc rereadrefs {} {
9527 global idtags idheads idotherrefs mainheadid
9529 set refids [concat [array names idtags] \
9530 [array names idheads] [array names idotherrefs]]
9531 foreach id $refids {
9532 if {![info exists ref($id)]} {
9533 set ref($id) [listrefs $id]
9536 set oldmainhead $mainheadid
9539 set refids [lsort -unique [concat $refids [array names idtags] \
9540 [array names idheads] [array names idotherrefs]]]
9541 foreach id $refids {
9542 set v [listrefs $id]
9543 if {![info exists ref($id)] || $ref($id) != $v} {
9547 if {$oldmainhead ne $mainheadid} {
9548 redrawtags $oldmainhead
9549 redrawtags $mainheadid
9554 proc listrefs {id} {
9555 global idtags idheads idotherrefs
9558 if {[info exists idtags($id)]} {
9562 if {[info exists idheads($id)]} {
9566 if {[info exists idotherrefs($id)]} {
9567 set z $idotherrefs($id)
9569 return [list $x $y $z]
9572 proc showtag {tag isnew} {
9573 global ctext tagcontents tagids linknum tagobjid
9576 addtohistory [list showtag $tag 0]
9578 $ctext conf -state normal
9582 if {![info exists tagcontents($tag)]} {
9584 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9587 if {[info exists tagcontents($tag)]} {
9588 set text $tagcontents($tag)
9590 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9592 appendwithlinks $text {}
9593 $ctext conf -state disabled
9605 if {[info exists gitktmpdir]} {
9606 catch {file delete -force $gitktmpdir}
9610 proc mkfontdisp {font top which} {
9611 global fontattr fontpref $font
9613 set fontpref($font) [set $font]
9614 button $top.${font}but -text $which -font optionfont \
9615 -command [list choosefont $font $which]
9616 label $top.$font -relief flat -font $font \
9617 -text $fontattr($font,family) -justify left
9618 grid x $top.${font}but $top.$font -sticky w
9621 proc choosefont {font which} {
9622 global fontparam fontlist fonttop fontattr
9624 set fontparam(which) $which
9625 set fontparam(font) $font
9626 set fontparam(family) [font actual $font -family]
9627 set fontparam(size) $fontattr($font,size)
9628 set fontparam(weight) $fontattr($font,weight)
9629 set fontparam(slant) $fontattr($font,slant)
9632 if {![winfo exists $top]} {
9634 eval font config sample [font actual $font]
9636 wm title $top [mc "Gitk font chooser"]
9637 label $top.l -textvariable fontparam(which)
9638 pack $top.l -side top
9639 set fontlist [lsort [font families]]
9641 listbox $top.f.fam -listvariable fontlist \
9642 -yscrollcommand [list $top.f.sb set]
9643 bind $top.f.fam <<ListboxSelect>> selfontfam
9644 scrollbar $top.f.sb -command [list $top.f.fam yview]
9645 pack $top.f.sb -side right -fill y
9646 pack $top.f.fam -side left -fill both -expand 1
9647 pack $top.f -side top -fill both -expand 1
9649 spinbox $top.g.size -from 4 -to 40 -width 4 \
9650 -textvariable fontparam(size) \
9651 -validatecommand {string is integer -strict %s}
9652 checkbutton $top.g.bold -padx 5 \
9653 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9654 -variable fontparam(weight) -onvalue bold -offvalue normal
9655 checkbutton $top.g.ital -padx 5 \
9656 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9657 -variable fontparam(slant) -onvalue italic -offvalue roman
9658 pack $top.g.size $top.g.bold $top.g.ital -side left
9659 pack $top.g -side top
9660 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9662 $top.c create text 100 25 -anchor center -text $which -font sample \
9663 -fill black -tags text
9664 bind $top.c <Configure> [list centertext $top.c]
9665 pack $top.c -side top -fill x
9667 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9668 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9669 grid $top.buts.ok $top.buts.can
9670 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9671 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9672 pack $top.buts -side bottom -fill x
9673 trace add variable fontparam write chg_fontparam
9676 $top.c itemconf text -text $which
9678 set i [lsearch -exact $fontlist $fontparam(family)]
9680 $top.f.fam selection set $i
9685 proc centertext {w} {
9686 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9690 global fontparam fontpref prefstop
9692 set f $fontparam(font)
9693 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9694 if {$fontparam(weight) eq "bold"} {
9695 lappend fontpref($f) "bold"
9697 if {$fontparam(slant) eq "italic"} {
9698 lappend fontpref($f) "italic"
9701 $w conf -text $fontparam(family) -font $fontpref($f)
9707 global fonttop fontparam
9709 if {[info exists fonttop]} {
9710 catch {destroy $fonttop}
9711 catch {font delete sample}
9717 proc selfontfam {} {
9718 global fonttop fontparam
9720 set i [$fonttop.f.fam curselection]
9722 set fontparam(family) [$fonttop.f.fam get $i]
9726 proc chg_fontparam {v sub op} {
9729 font config sample -$sub $fontparam($sub)
9733 global maxwidth maxgraphpct
9734 global oldprefs prefstop showneartags showlocalchanges
9735 global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
9736 global tabstop limitdiffs autoselect extdifftool perfile_attrs
9740 if {[winfo exists $top]} {
9744 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9745 limitdiffs tabstop perfile_attrs} {
9746 set oldprefs($v) [set $v]
9749 wm title $top [mc "Gitk preferences"]
9750 label $top.ldisp -text [mc "Commit list display options"]
9751 grid $top.ldisp - -sticky w -pady 10
9752 label $top.spacer -text " "
9753 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9755 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9756 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9757 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9759 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9760 grid x $top.maxpctl $top.maxpct -sticky w
9761 frame $top.showlocal
9762 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9763 checkbutton $top.showlocal.b -variable showlocalchanges
9764 pack $top.showlocal.b $top.showlocal.l -side left
9765 grid x $top.showlocal -sticky w
9766 frame $top.autoselect
9767 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9768 checkbutton $top.autoselect.b -variable autoselect
9769 pack $top.autoselect.b $top.autoselect.l -side left
9770 grid x $top.autoselect -sticky w
9772 label $top.ddisp -text [mc "Diff display options"]
9773 grid $top.ddisp - -sticky w -pady 10
9774 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9775 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9776 grid x $top.tabstopl $top.tabstop -sticky w
9778 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9779 checkbutton $top.ntag.b -variable showneartags
9780 pack $top.ntag.b $top.ntag.l -side left
9781 grid x $top.ntag -sticky w
9783 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9784 checkbutton $top.ldiff.b -variable limitdiffs
9785 pack $top.ldiff.b $top.ldiff.l -side left
9786 grid x $top.ldiff -sticky w
9788 label $top.lattr.l -text [mc "Support per-file encodings"] -font optionfont
9789 checkbutton $top.lattr.b -variable perfile_attrs
9790 pack $top.lattr.b $top.lattr.l -side left
9791 grid x $top.lattr -sticky w
9793 entry $top.extdifft -textvariable extdifftool
9795 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
9797 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
9798 -command choose_extdiff
9799 pack $top.extdifff.l $top.extdifff.b -side left
9800 grid x $top.extdifff $top.extdifft -sticky w
9802 label $top.cdisp -text [mc "Colors: press to choose"]
9803 grid $top.cdisp - -sticky w -pady 10
9804 label $top.bg -padx 40 -relief sunk -background $bgcolor
9805 button $top.bgbut -text [mc "Background"] -font optionfont \
9806 -command [list choosecolor bgcolor {} $top.bg background setbg]
9807 grid x $top.bgbut $top.bg -sticky w
9808 label $top.fg -padx 40 -relief sunk -background $fgcolor
9809 button $top.fgbut -text [mc "Foreground"] -font optionfont \
9810 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
9811 grid x $top.fgbut $top.fg -sticky w
9812 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
9813 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
9814 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
9815 [list $ctext tag conf d0 -foreground]]
9816 grid x $top.diffoldbut $top.diffold -sticky w
9817 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
9818 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
9819 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
9820 [list $ctext tag conf d1 -foreground]]
9821 grid x $top.diffnewbut $top.diffnew -sticky w
9822 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
9823 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
9824 -command [list choosecolor diffcolors 2 $top.hunksep \
9825 "diff hunk header" \
9826 [list $ctext tag conf hunksep -foreground]]
9827 grid x $top.hunksepbut $top.hunksep -sticky w
9828 label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
9829 button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \
9830 -command [list choosecolor markbgcolor {} $top.markbgsep \
9831 [mc "marked line background"] \
9832 [list $ctext tag conf omark -background]]
9833 grid x $top.markbgbut $top.markbgsep -sticky w
9834 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
9835 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
9836 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
9837 grid x $top.selbgbut $top.selbgsep -sticky w
9839 label $top.cfont -text [mc "Fonts: press to choose"]
9840 grid $top.cfont - -sticky w -pady 10
9841 mkfontdisp mainfont $top [mc "Main font"]
9842 mkfontdisp textfont $top [mc "Diff display font"]
9843 mkfontdisp uifont $top [mc "User interface font"]
9846 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
9847 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
9848 grid $top.buts.ok $top.buts.can
9849 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9850 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9851 grid $top.buts - - -pady 10 -sticky ew
9852 bind $top <Visibility> "focus $top.buts.ok"
9855 proc choose_extdiff {} {
9858 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
9860 set extdifftool $prog
9864 proc choosecolor {v vi w x cmd} {
9867 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
9868 -title [mc "Gitk: choose color for %s" $x]]
9869 if {$c eq {}} return
9870 $w conf -background $c
9876 global bglist cflist
9878 $w configure -selectbackground $c
9880 $cflist tag configure highlight \
9881 -background [$cflist cget -selectbackground]
9882 allcanvs itemconf secsel -fill $c
9889 $w conf -background $c
9897 $w conf -foreground $c
9899 allcanvs itemconf text -fill $c
9900 $canv itemconf circle -outline $c
9904 global oldprefs prefstop
9906 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9907 limitdiffs tabstop perfile_attrs} {
9909 set $v $oldprefs($v)
9911 catch {destroy $prefstop}
9917 global maxwidth maxgraphpct
9918 global oldprefs prefstop showneartags showlocalchanges
9919 global fontpref mainfont textfont uifont
9920 global limitdiffs treediffs perfile_attrs
9922 catch {destroy $prefstop}
9926 if {$mainfont ne $fontpref(mainfont)} {
9927 set mainfont $fontpref(mainfont)
9928 parsefont mainfont $mainfont
9929 eval font configure mainfont [fontflags mainfont]
9930 eval font configure mainfontbold [fontflags mainfont 1]
9934 if {$textfont ne $fontpref(textfont)} {
9935 set textfont $fontpref(textfont)
9936 parsefont textfont $textfont
9937 eval font configure textfont [fontflags textfont]
9938 eval font configure textfontbold [fontflags textfont 1]
9940 if {$uifont ne $fontpref(uifont)} {
9941 set uifont $fontpref(uifont)
9942 parsefont uifont $uifont
9943 eval font configure uifont [fontflags uifont]
9946 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
9947 if {$showlocalchanges} {
9953 if {$limitdiffs != $oldprefs(limitdiffs) ||
9954 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
9955 # treediffs elements are limited by path;
9956 # won't have encodings cached if perfile_attrs was just turned on
9957 catch {unset treediffs}
9959 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
9960 || $maxgraphpct != $oldprefs(maxgraphpct)} {
9962 } elseif {$showneartags != $oldprefs(showneartags) ||
9963 $limitdiffs != $oldprefs(limitdiffs)} {
9968 proc formatdate {d} {
9969 global datetimeformat
9971 set d [clock format $d -format $datetimeformat]
9976 # This list of encoding names and aliases is distilled from
9977 # http://www.iana.org/assignments/character-sets.
9978 # Not all of them are supported by Tcl.
9979 set encoding_aliases {
9980 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
9981 ISO646-US US-ASCII us IBM367 cp367 csASCII }
9982 { ISO-10646-UTF-1 csISO10646UTF1 }
9983 { ISO_646.basic:1983 ref csISO646basic1983 }
9984 { INVARIANT csINVARIANT }
9985 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
9986 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
9987 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
9988 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
9989 { NATS-DANO iso-ir-9-1 csNATSDANO }
9990 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
9991 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
9992 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
9993 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
9994 { ISO-2022-KR csISO2022KR }
9996 { ISO-2022-JP csISO2022JP }
9997 { ISO-2022-JP-2 csISO2022JP2 }
9998 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
10000 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
10001 { IT iso-ir-15 ISO646-IT csISO15Italian }
10002 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
10003 { ES iso-ir-17 ISO646-ES csISO17Spanish }
10004 { greek7-old iso-ir-18 csISO18Greek7Old }
10005 { latin-greek iso-ir-19 csISO19LatinGreek }
10006 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
10007 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
10008 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
10009 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
10010 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
10011 { BS_viewdata iso-ir-47 csISO47BSViewdata }
10012 { INIS iso-ir-49 csISO49INIS }
10013 { INIS-8 iso-ir-50 csISO50INIS8 }
10014 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
10015 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
10016 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
10017 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
10018 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
10019 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
10020 csISO60Norwegian1 }
10021 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
10022 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
10023 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
10024 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
10025 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
10026 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
10027 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
10028 { greek7 iso-ir-88 csISO88Greek7 }
10029 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
10030 { iso-ir-90 csISO90 }
10031 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
10032 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
10033 csISO92JISC62991984b }
10034 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
10035 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
10036 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
10037 csISO95JIS62291984handadd }
10038 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
10039 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
10040 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
10041 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
10042 CP819 csISOLatin1 }
10043 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
10044 { T.61-7bit iso-ir-102 csISO102T617bit }
10045 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
10046 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
10047 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
10048 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
10049 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
10050 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
10051 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
10052 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
10053 arabic csISOLatinArabic }
10054 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
10055 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
10056 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
10057 greek greek8 csISOLatinGreek }
10058 { T.101-G2 iso-ir-128 csISO128T101G2 }
10059 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
10061 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
10062 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
10063 { CSN_369103 iso-ir-139 csISO139CSN369103 }
10064 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
10065 { ISO_6937-2-add iso-ir-142 csISOTextComm }
10066 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
10067 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
10068 csISOLatinCyrillic }
10069 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
10070 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
10071 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
10072 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
10073 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
10074 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
10075 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
10076 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
10077 { ISO_10367-box iso-ir-155 csISO10367Box }
10078 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
10079 { latin-lap lap iso-ir-158 csISO158Lap }
10080 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
10081 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
10084 { JIS_X0201 X0201 csHalfWidthKatakana }
10085 { KSC5636 ISO646-KR csKSC5636 }
10086 { ISO-10646-UCS-2 csUnicode }
10087 { ISO-10646-UCS-4 csUCS4 }
10088 { DEC-MCS dec csDECMCS }
10089 { hp-roman8 roman8 r8 csHPRoman8 }
10090 { macintosh mac csMacintosh }
10091 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
10093 { IBM038 EBCDIC-INT cp038 csIBM038 }
10094 { IBM273 CP273 csIBM273 }
10095 { IBM274 EBCDIC-BE CP274 csIBM274 }
10096 { IBM275 EBCDIC-BR cp275 csIBM275 }
10097 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
10098 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
10099 { IBM280 CP280 ebcdic-cp-it csIBM280 }
10100 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
10101 { IBM284 CP284 ebcdic-cp-es csIBM284 }
10102 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
10103 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
10104 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
10105 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
10106 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
10107 { IBM424 cp424 ebcdic-cp-he csIBM424 }
10108 { IBM437 cp437 437 csPC8CodePage437 }
10109 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
10110 { IBM775 cp775 csPC775Baltic }
10111 { IBM850 cp850 850 csPC850Multilingual }
10112 { IBM851 cp851 851 csIBM851 }
10113 { IBM852 cp852 852 csPCp852 }
10114 { IBM855 cp855 855 csIBM855 }
10115 { IBM857 cp857 857 csIBM857 }
10116 { IBM860 cp860 860 csIBM860 }
10117 { IBM861 cp861 861 cp-is csIBM861 }
10118 { IBM862 cp862 862 csPC862LatinHebrew }
10119 { IBM863 cp863 863 csIBM863 }
10120 { IBM864 cp864 csIBM864 }
10121 { IBM865 cp865 865 csIBM865 }
10122 { IBM866 cp866 866 csIBM866 }
10123 { IBM868 CP868 cp-ar csIBM868 }
10124 { IBM869 cp869 869 cp-gr csIBM869 }
10125 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
10126 { IBM871 CP871 ebcdic-cp-is csIBM871 }
10127 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
10128 { IBM891 cp891 csIBM891 }
10129 { IBM903 cp903 csIBM903 }
10130 { IBM904 cp904 904 csIBBM904 }
10131 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
10132 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
10133 { IBM1026 CP1026 csIBM1026 }
10134 { EBCDIC-AT-DE csIBMEBCDICATDE }
10135 { EBCDIC-AT-DE-A csEBCDICATDEA }
10136 { EBCDIC-CA-FR csEBCDICCAFR }
10137 { EBCDIC-DK-NO csEBCDICDKNO }
10138 { EBCDIC-DK-NO-A csEBCDICDKNOA }
10139 { EBCDIC-FI-SE csEBCDICFISE }
10140 { EBCDIC-FI-SE-A csEBCDICFISEA }
10141 { EBCDIC-FR csEBCDICFR }
10142 { EBCDIC-IT csEBCDICIT }
10143 { EBCDIC-PT csEBCDICPT }
10144 { EBCDIC-ES csEBCDICES }
10145 { EBCDIC-ES-A csEBCDICESA }
10146 { EBCDIC-ES-S csEBCDICESS }
10147 { EBCDIC-UK csEBCDICUK }
10148 { EBCDIC-US csEBCDICUS }
10149 { UNKNOWN-8BIT csUnknown8BiT }
10150 { MNEMONIC csMnemonic }
10152 { VISCII csVISCII }
10155 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
10156 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
10157 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
10158 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
10159 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
10160 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
10161 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
10162 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
10163 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
10164 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
10165 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
10166 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
10167 { IBM1047 IBM-1047 }
10168 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
10169 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
10170 { UNICODE-1-1 csUnicode11 }
10171 { CESU-8 csCESU-8 }
10172 { BOCU-1 csBOCU-1 }
10173 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
10174 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
10176 { ISO-8859-15 ISO_8859-15 Latin-9 }
10177 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
10178 { GBK CP936 MS936 windows-936 }
10179 { JIS_Encoding csJISEncoding }
10180 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
10181 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
10183 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
10184 { ISO-10646-UCS-Basic csUnicodeASCII }
10185 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
10186 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
10187 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
10188 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
10189 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
10190 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
10191 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
10192 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
10193 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
10194 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
10195 { Adobe-Standard-Encoding csAdobeStandardEncoding }
10196 { Ventura-US csVenturaUS }
10197 { Ventura-International csVenturaInternational }
10198 { PC8-Danish-Norwegian csPC8DanishNorwegian }
10199 { PC8-Turkish csPC8Turkish }
10200 { IBM-Symbols csIBMSymbols }
10201 { IBM-Thai csIBMThai }
10202 { HP-Legal csHPLegal }
10203 { HP-Pi-font csHPPiFont }
10204 { HP-Math8 csHPMath8 }
10205 { Adobe-Symbol-Encoding csHPPSMath }
10206 { HP-DeskTop csHPDesktop }
10207 { Ventura-Math csVenturaMath }
10208 { Microsoft-Publishing csMicrosoftPublishing }
10209 { Windows-31J csWindows31J }
10210 { GB2312 csGB2312 }
10214 proc tcl_encoding {enc} {
10215 global encoding_aliases tcl_encoding_cache
10216 if {[info exists tcl_encoding_cache($enc)]} {
10217 return $tcl_encoding_cache($enc)
10219 set names [encoding names]
10220 set lcnames [string tolower $names]
10221 set enc [string tolower $enc]
10222 set i [lsearch -exact $lcnames $enc]
10224 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
10225 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
10226 set i [lsearch -exact $lcnames $encx]
10230 foreach l $encoding_aliases {
10231 set ll [string tolower $l]
10232 if {[lsearch -exact $ll $enc] < 0} continue
10233 # look through the aliases for one that tcl knows about
10235 set i [lsearch -exact $lcnames $e]
10237 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
10238 set i [lsearch -exact $lcnames $ex]
10248 set tclenc [lindex $names $i]
10250 set tcl_encoding_cache($enc) $tclenc
10254 proc gitattr {path attr default} {
10255 global path_attr_cache
10256 if {[info exists path_attr_cache($attr,$path)]} {
10257 set r $path_attr_cache($attr,$path)
10259 set r "unspecified"
10260 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
10261 regexp "(.*): encoding: (.*)" $line m f r
10263 set path_attr_cache($attr,$path) $r
10265 if {$r eq "unspecified"} {
10271 proc cache_gitattr {attr pathlist} {
10272 global path_attr_cache
10274 foreach path $pathlist {
10275 if {![info exists path_attr_cache($attr,$path)]} {
10276 lappend newlist $path
10280 if {[tk windowingsystem] == "win32"} {
10281 # windows has a 32k limit on the arguments to a command...
10284 while {$newlist ne {}} {
10285 set head [lrange $newlist 0 [expr {$lim - 1}]]
10286 set newlist [lrange $newlist $lim end]
10287 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
10288 foreach row [split $rlist "\n"] {
10289 if {[regexp "(.*): encoding: (.*)" $row m path value]} {
10290 if {[string index $path 0] eq "\""} {
10291 set path [encoding convertfrom [lindex $path 0]]
10293 set path_attr_cache($attr,$path) $value
10300 proc get_path_encoding {path} {
10301 global gui_encoding perfile_attrs
10302 set tcl_enc $gui_encoding
10303 if {$path ne {} && $perfile_attrs} {
10304 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
10312 # First check that Tcl/Tk is recent enough
10313 if {[catch {package require Tk 8.4} err]} {
10314 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
10315 Gitk requires at least Tcl/Tk 8.4."]
10320 set wrcomcmd "git diff-tree --stdin -p --pretty"
10324 set gitencoding [exec git config --get i18n.commitencoding]
10326 if {$gitencoding == ""} {
10327 set gitencoding "utf-8"
10329 set tclencoding [tcl_encoding $gitencoding]
10330 if {$tclencoding == {}} {
10331 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
10334 set gui_encoding [encoding system]
10336 set enc [exec git config --get gui.encoding]
10338 set tclenc [tcl_encoding $enc]
10339 if {$tclenc ne {}} {
10340 set gui_encoding $tclenc
10342 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
10347 set mainfont {Helvetica 9}
10348 set textfont {Courier 9}
10349 set uifont {Helvetica 9 bold}
10351 set findmergefiles 0
10359 set cmitmode "patch"
10360 set wrapcomment "none"
10364 set showlocalchanges 1
10366 set datetimeformat "%Y-%m-%d %H:%M:%S"
10368 set perfile_attrs 0
10370 set extdifftool "meld"
10372 set colors {green red blue magenta darkgrey brown orange}
10375 set diffcolors {red "#00a000" blue}
10378 set selectbgcolor gray85
10379 set markbgcolor "#e0e0ff"
10381 set circlecolors {white blue gray blue blue}
10383 # button for popping up context menus
10384 if {[tk windowingsystem] eq "aqua"} {
10385 set ctxbut <Button-2>
10387 set ctxbut <Button-3>
10390 ## For msgcat loading, first locate the installation location.
10391 if { [info exists ::env(GITK_MSGSDIR)] } {
10392 ## Msgsdir was manually set in the environment.
10393 set gitk_msgsdir $::env(GITK_MSGSDIR)
10395 ## Let's guess the prefix from argv0.
10396 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
10397 set gitk_libdir [file join $gitk_prefix share gitk lib]
10398 set gitk_msgsdir [file join $gitk_libdir msgs]
10402 ## Internationalization (i18n) through msgcat and gettext. See
10403 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
10404 package require msgcat
10405 namespace import ::msgcat::mc
10406 ## And eventually load the actual message catalog
10407 ::msgcat::mcload $gitk_msgsdir
10409 catch {source ~/.gitk}
10411 font create optionfont -family sans-serif -size -12
10413 parsefont mainfont $mainfont
10414 eval font create mainfont [fontflags mainfont]
10415 eval font create mainfontbold [fontflags mainfont 1]
10417 parsefont textfont $textfont
10418 eval font create textfont [fontflags textfont]
10419 eval font create textfontbold [fontflags textfont 1]
10421 parsefont uifont $uifont
10422 eval font create uifont [fontflags uifont]
10426 # check that we can find a .git directory somewhere...
10427 if {[catch {set gitdir [gitdir]}]} {
10428 show_error {} . [mc "Cannot find a git repository here."]
10431 if {![file isdirectory $gitdir]} {
10432 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
10437 set selectheadid {}
10440 set cmdline_files {}
10442 set revtreeargscmd {}
10443 foreach arg $argv {
10444 switch -glob -- $arg {
10447 set cmdline_files [lrange $argv [expr {$i + 1}] end]
10450 "--select-commit=*" {
10451 set selecthead [string range $arg 16 end]
10454 set revtreeargscmd [string range $arg 10 end]
10457 lappend revtreeargs $arg
10463 if {$selecthead eq "HEAD"} {
10467 if {$i >= [llength $argv] && $revtreeargs ne {}} {
10468 # no -- on command line, but some arguments (other than --argscmd)
10470 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
10471 set cmdline_files [split $f "\n"]
10472 set n [llength $cmdline_files]
10473 set revtreeargs [lrange $revtreeargs 0 end-$n]
10474 # Unfortunately git rev-parse doesn't produce an error when
10475 # something is both a revision and a filename. To be consistent
10476 # with git log and git rev-list, check revtreeargs for filenames.
10477 foreach arg $revtreeargs {
10478 if {[file exists $arg]} {
10479 show_error {} . [mc "Ambiguous argument '%s': both revision\
10480 and filename" $arg]
10485 # unfortunately we get both stdout and stderr in $err,
10486 # so look for "fatal:".
10487 set i [string first "fatal:" $err]
10489 set err [string range $err [expr {$i + 6}] end]
10491 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
10496 set nullid "0000000000000000000000000000000000000000"
10497 set nullid2 "0000000000000000000000000000000000000001"
10498 set nullfile "/dev/null"
10500 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
10507 set highlight_paths {}
10509 set searchdirn -forwards
10511 set boldnamerows {}
10512 set diffelide {0 0}
10513 set markingmatches 0
10514 set linkentercount 0
10515 set need_redisplay 0
10522 set selectedhlview [mc "None"]
10523 set highlight_related [mc "None"]
10524 set highlight_files {}
10525 set viewfiles(0) {}
10528 set viewargscmd(0) {}
10530 set selectedline {}
10538 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
10541 # wait for the window to become visible
10542 tkwait visibility .
10543 wm title . "[file tail $argv0]: [file tail [pwd]]"
10546 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
10547 # create a view for the files/dirs specified on the command line
10551 set viewname(1) [mc "Command line"]
10552 set viewfiles(1) $cmdline_files
10553 set viewargs(1) $revtreeargs
10554 set viewargscmd(1) $revtreeargscmd
10558 .bar.view entryconf [mca "Edit view..."] -state normal
10559 .bar.view entryconf [mca "Delete view"] -state normal
10562 if {[info exists permviews]} {
10563 foreach v $permviews {
10566 set viewname($n) [lindex $v 0]
10567 set viewfiles($n) [lindex $v 1]
10568 set viewargs($n) [lindex $v 2]
10569 set viewargscmd($n) [lindex $v 3]