2 # Tcl ignores the next line -*- tcl -*- \
5 # Copyright © 2005-2008 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
12 if {[info exists env
(GIT_DIR
)]} {
15 return [exec git rev-parse
--git-dir]
19 # A simple scheduler for compute-intensive stuff.
20 # The aim is to make sure that event handlers for GUI actions can
21 # run at least every 50-100 ms. Unfortunately fileevent handlers are
22 # run before X event handlers, so reading from a fast source can
23 # make the GUI completely unresponsive.
28 if {[info exists isonrunq
($script)]} return
32 lappend runq
[list
{} $script]
33 set isonrunq
($script) 1
36 proc filerun
{fd
script} {
37 fileevent
$fd readable
[list filereadable
$fd $script]
40 proc filereadable
{fd
script} {
43 fileevent
$fd readable
{}
47 lappend runq
[list
$fd $script]
53 for {set i
0} {$i < [llength
$runq]} {} {
54 if {[lindex
$runq $i 0] eq
$fd} {
55 set runq
[lreplace
$runq $i $i]
65 set tstart
[clock clicks
-milliseconds]
67 while {[llength
$runq] > 0} {
68 set fd
[lindex
$runq 0 0]
69 set script [lindex
$runq 0 1]
70 set repeat
[eval $script]
71 set t1
[clock clicks
-milliseconds]
72 set t
[expr {$t1 - $t0}]
73 set runq
[lrange
$runq 1 end
]
74 if {$repeat ne
{} && $repeat} {
75 if {$fd eq
{} ||
$repeat == 2} {
76 # script returns 1 if it wants to be readded
77 # file readers return 2 if they could do more straight away
78 lappend runq
[list
$fd $script]
80 fileevent
$fd readable
[list filereadable
$fd $script]
82 } elseif
{$fd eq
{}} {
83 unset isonrunq
($script)
86 if {$t1 - $tstart >= 80} break
93 proc unmerged_files
{files
} {
96 # find the list of unmerged files
100 set fd
[open
"| git ls-files -u" r
]
102 show_error
{} .
"[mc "Couldn
't get list of unmerged files:"] $err"
105 while {[gets $fd line] >= 0} {
106 set i [string first "\t" $line]
108 set fname [string range $line [expr {$i+1}] end]
109 if {[lsearch -exact $mlist $fname] >= 0} continue
111 if {$files eq {} || [path_filter $files $fname]} {
119 proc parseviewargs {n arglist} {
120 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
128 set origargs $arglist
132 foreach arg $arglist {
139 switch -glob -- $arg {
143 # remove from origargs in case we hit an unknown option
144 set origargs [lreplace $origargs $i $i]
147 # These request or affect diff output, which we don't want.
148 # Some could be used to set our defaults for diff display.
150 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
151 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
152 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
153 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
154 "--ignore-space-change" - "-U*" - "--unified=*" {
155 lappend diffargs
$arg
157 # These cause our parsing of git log's output to fail, or else
158 # they're options we want to set ourselves, so ignore them.
159 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
160 "--name-only" - "--name-status" - "--color" - "--color-words" -
161 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
162 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
163 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
164 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
165 "--objects" - "--objects-edge" - "--reverse" {
167 # These are harmless, and some are even useful
168 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
169 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
170 "--full-history" - "--dense" - "--sparse" -
171 "--follow" - "--left-right" - "--encoding=*" {
174 # These mean that we get a subset of the commits
175 "--diff-filter=*" - "--no-merges" - "--unpacked" -
176 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
177 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
178 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
179 "--remove-empty" - "--first-parent" - "--cherry-pick" -
180 "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
184 # This appears to be the only one that has a value as a
185 # separate word following it
192 set notflag
[expr {!$notflag}]
200 # git rev-parse doesn't understand --merge
201 lappend revargs
--gitk-symmetric-diff-marker MERGE_HEAD...HEAD
203 # Other flag arguments including -<n>
205 if {[string is digit
-strict [string range
$arg 1 end
]]} {
208 # a flag argument that we don't recognize;
209 # that means we can't optimize
214 # Non-flag arguments specify commits or ranges of commits
216 if {[string match
"*...*" $arg]} {
217 lappend revargs
--gitk-symmetric-diff-marker
223 set vdflags
($n) $diffargs
224 set vflags
($n) $glflags
225 set vrevs
($n) $revargs
226 set vfiltered
($n) $filtered
227 set vorigargs
($n) $origargs
231 proc parseviewrevs
{view revs
} {
232 global vposids vnegids
237 if {[catch
{set ids
[eval exec git rev-parse
$revs]} err
]} {
238 # we get stdout followed by stderr in $err
239 # for an unknown rev, git rev-parse echoes it and then errors out
240 set errlines
[split $err "\n"]
242 for {set l
0} {$l < [llength
$errlines]} {incr l
} {
243 set line
[lindex
$errlines $l]
244 if {!([string length
$line] == 40 && [string is xdigit
$line])} {
245 if {[string match
"fatal:*" $line]} {
246 if {[string match
"fatal: ambiguous argument*" $line]
248 if {[llength
$badrev] == 1} {
249 set err
"unknown revision $badrev"
251 set err
"unknown revisions: [join $badrev ", "]"
254 set err
[join [lrange
$errlines $l end
] "\n"]
261 error_popup
"Error parsing revisions: $err"
268 foreach id
[split $ids "\n"] {
269 if {$id eq
"--gitk-symmetric-diff-marker"} {
271 } elseif
{[string match
"^*" $id]} {
278 lappend neg
[string range
$id 1 end
]
283 lset ret end
[lindex
$ret end
]...
$id
289 set vposids
($view) $pos
290 set vnegids
($view) $neg
294 # Start off a git log process and arrange to read its output
295 proc start_rev_list
{view
} {
296 global startmsecs commitidx viewcomplete curview
297 global commfd leftover tclencoding
298 global viewargs viewargscmd viewfiles vfilelimit
299 global showlocalchanges commitinterest mainheadid
300 global viewactive loginstance viewinstances vmergeonly
301 global pending_select mainheadid
302 global vcanopt vflags vrevs vorigargs
304 set startmsecs
[clock clicks
-milliseconds]
305 set commitidx
($view) 0
306 # these are set this way for the error exits
307 set viewcomplete
($view) 1
308 set viewactive
($view) 0
311 set args
$viewargs($view)
312 if {$viewargscmd($view) ne
{}} {
314 set str
[exec sh
-c $viewargscmd($view)]
316 error_popup
"Error executing --argscmd command: $err"
319 set args
[concat
$args [split $str "\n"]]
321 set vcanopt
($view) [parseviewargs
$view $args]
323 set files
$viewfiles($view)
324 if {$vmergeonly($view)} {
325 set files
[unmerged_files
$files]
328 if {$nr_unmerged == 0} {
329 error_popup
[mc
"No files selected: --merge specified but\
330 no files are unmerged."]
332 error_popup
[mc
"No files selected: --merge specified but\
333 no unmerged files are within file limit."]
338 set vfilelimit
($view) $files
340 if {$vcanopt($view)} {
341 set revs
[parseviewrevs
$view $vrevs($view)]
345 set args
[concat
$vflags($view) $revs]
347 set args
$vorigargs($view)
351 set fd
[open
[concat | git log
--no-color -z --pretty=raw
--parents \
352 --boundary $args "--" $files] r
]
354 error_popup
"[mc "Error executing git log
:"] $err"
357 set i
[incr loginstance
]
358 set viewinstances
($view) [list
$i]
361 if {$showlocalchanges} {
362 lappend commitinterest
($mainheadid) {dodiffindex
}
364 fconfigure
$fd -blocking 0 -translation lf
-eofchar {}
365 if {$tclencoding != {}} {
366 fconfigure
$fd -encoding $tclencoding
368 filerun
$fd [list getcommitlines
$fd $i $view 0]
369 nowbusy
$view [mc
"Reading"]
370 if {$view == $curview} {
371 set pending_select
$mainheadid
373 set viewcomplete
($view) 0
374 set viewactive
($view) 1
378 proc stop_rev_list
{view
} {
379 global commfd viewinstances leftover
381 foreach inst
$viewinstances($view) {
382 set fd
$commfd($inst)
390 unset leftover
($inst)
392 set viewinstances
($view) {}
396 global canv curview need_redisplay viewactive
399 if {[start_rev_list
$curview]} {
400 show_status
[mc
"Reading commits..."]
403 show_status
[mc
"No commits selected"]
407 proc updatecommits
{} {
408 global curview vcanopt vorigargs vfilelimit viewinstances
409 global viewactive viewcomplete loginstance tclencoding mainheadid
410 global startmsecs commfd showneartags showlocalchanges leftover
411 global mainheadid pending_select
413 global varcid vposids vnegids vflags vrevs
415 set isworktree
[expr {[exec git rev-parse
--is-inside-work-tree] == "true"}]
416 set oldmainid
$mainheadid
418 if {$showlocalchanges} {
419 if {$mainheadid ne
$oldmainid} {
422 if {[commitinview
$mainheadid $curview]} {
427 if {$vcanopt($view)} {
428 set oldpos
$vposids($view)
429 set oldneg
$vnegids($view)
430 set revs
[parseviewrevs
$view $vrevs($view)]
434 # note: getting the delta when negative refs change is hard,
435 # and could require multiple git log invocations, so in that
436 # case we ask git log for all the commits (not just the delta)
437 if {$oldneg eq
$vnegids($view)} {
440 # take out positive refs that we asked for before or
441 # that we have already seen
443 if {[string length
$rev] == 40} {
444 if {[lsearch
-exact $oldpos $rev] < 0
445 && ![info exists varcid
($view,$rev)]} {
450 lappend
$newrevs $rev
453 if {$npos == 0} return
455 set vposids
($view) [lsort
-unique [concat
$oldpos $vposids($view)]]
457 set args
[concat
$vflags($view) $revs --not $oldpos]
459 set args
$vorigargs($view)
462 set fd
[open
[concat | git log
--no-color -z --pretty=raw
--parents \
463 --boundary $args "--" $vfilelimit($view)] r
]
465 error_popup
"Error executing git log: $err"
468 if {$viewactive($view) == 0} {
469 set startmsecs
[clock clicks
-milliseconds]
471 set i
[incr loginstance
]
472 lappend viewinstances
($view) $i
475 fconfigure
$fd -blocking 0 -translation lf
-eofchar {}
476 if {$tclencoding != {}} {
477 fconfigure
$fd -encoding $tclencoding
479 filerun
$fd [list getcommitlines
$fd $i $view 1]
480 incr viewactive
($view)
481 set viewcomplete
($view) 0
482 set pending_select
$mainheadid
483 nowbusy
$view "Reading"
489 proc reloadcommits
{} {
490 global curview viewcomplete selectedline currentid thickerline
491 global showneartags treediffs commitinterest cached_commitrow
494 if {!$viewcomplete($curview)} {
495 stop_rev_list
$curview
498 catch
{unset selectedline
}
499 catch
{unset currentid
}
500 catch
{unset thickerline
}
501 catch
{unset treediffs
}
508 catch
{unset commitinterest
}
509 catch
{unset cached_commitrow
}
510 catch
{unset targetid
}
516 # This makes a string representation of a positive integer which
517 # sorts as a string in numerical order
520 return [format
"%x" $n]
521 } elseif
{$n < 256} {
522 return [format
"x%.2x" $n]
523 } elseif
{$n < 65536} {
524 return [format
"y%.4x" $n]
526 return [format
"z%.8x" $n]
529 # Procedures used in reordering commits from git log (without
530 # --topo-order) into the order for display.
532 proc varcinit
{view
} {
533 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
534 global vtokmod varcmod vrowmod varcix vlastins
536 set varcstart
($view) {{}}
537 set vupptr
($view) {0}
538 set vdownptr
($view) {0}
539 set vleftptr
($view) {0}
540 set vbackptr
($view) {0}
541 set varctok
($view) {{}}
542 set varcrow
($view) {{}}
543 set vtokmod
($view) {}
546 set varcix
($view) {{}}
547 set vlastins
($view) {0}
550 proc resetvarcs
{view
} {
551 global varcid varccommits parents children vseedcount ordertok
553 foreach vid
[array names varcid
$view,*] {
558 # some commits might have children but haven't been seen yet
559 foreach vid
[array names children
$view,*] {
562 foreach va
[array names varccommits
$view,*] {
563 unset varccommits
($va)
565 foreach vd
[array names vseedcount
$view,*] {
566 unset vseedcount
($vd)
568 catch
{unset ordertok
}
571 # returns a list of the commits with no children
573 global vdownptr vleftptr varcstart
576 set a
[lindex
$vdownptr($v) 0]
578 lappend ret
[lindex
$varcstart($v) $a]
579 set a
[lindex
$vleftptr($v) $a]
584 proc newvarc
{view id
} {
585 global varcid varctok parents children vdatemode
586 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
587 global commitdata commitinfo vseedcount varccommits vlastins
589 set a
[llength
$varctok($view)]
591 if {[llength
$children($vid)] == 0 ||
$vdatemode($view)} {
592 if {![info exists commitinfo
($id)]} {
593 parsecommit
$id $commitdata($id) 1
595 set cdate
[lindex
$commitinfo($id) 4]
596 if {![string is integer
-strict $cdate]} {
599 if {![info exists vseedcount
($view,$cdate)]} {
600 set vseedcount
($view,$cdate) -1
602 set c
[incr vseedcount
($view,$cdate)]
603 set cdate
[expr {$cdate ^
0xffffffff}]
604 set tok
"s[strrep $cdate][strrep $c]"
609 if {[llength
$children($vid)] > 0} {
610 set kid
[lindex
$children($vid) end
]
611 set k
$varcid($view,$kid)
612 if {[string compare
[lindex
$varctok($view) $k] $tok] > 0} {
615 set tok
[lindex
$varctok($view) $k]
619 set i
[lsearch
-exact $parents($view,$ki) $id]
620 set j
[expr {[llength
$parents($view,$ki)] - 1 - $i}]
621 append tok
[strrep
$j]
623 set c
[lindex
$vlastins($view) $ka]
624 if {$c == 0 ||
[string compare
$tok [lindex
$varctok($view) $c]] < 0} {
626 set b
[lindex
$vdownptr($view) $ka]
628 set b
[lindex
$vleftptr($view) $c]
630 while {$b != 0 && [string compare
$tok [lindex
$varctok($view) $b]] >= 0} {
632 set b
[lindex
$vleftptr($view) $c]
635 lset vdownptr
($view) $ka $a
636 lappend vbackptr
($view) 0
638 lset vleftptr
($view) $c $a
639 lappend vbackptr
($view) $c
641 lset vlastins
($view) $ka $a
642 lappend vupptr
($view) $ka
643 lappend vleftptr
($view) $b
645 lset vbackptr
($view) $b $a
647 lappend varctok
($view) $tok
648 lappend varcstart
($view) $id
649 lappend vdownptr
($view) 0
650 lappend varcrow
($view) {}
651 lappend varcix
($view) {}
652 set varccommits
($view,$a) {}
653 lappend vlastins
($view) 0
657 proc splitvarc
{p v
} {
658 global varcid varcstart varccommits varctok
659 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
661 set oa
$varcid($v,$p)
662 set ac
$varccommits($v,$oa)
663 set i
[lsearch
-exact $varccommits($v,$oa) $p]
665 set na
[llength
$varctok($v)]
666 # "%" sorts before "0"...
667 set tok
"[lindex $varctok($v) $oa]%[strrep $i]"
668 lappend varctok
($v) $tok
669 lappend varcrow
($v) {}
670 lappend varcix
($v) {}
671 set varccommits
($v,$oa) [lrange
$ac 0 [expr {$i - 1}]]
672 set varccommits
($v,$na) [lrange
$ac $i end
]
673 lappend varcstart
($v) $p
674 foreach id
$varccommits($v,$na) {
675 set varcid
($v,$id) $na
677 lappend vdownptr
($v) [lindex
$vdownptr($v) $oa]
678 lappend vlastins
($v) [lindex
$vlastins($v) $oa]
679 lset vdownptr
($v) $oa $na
680 lset vlastins
($v) $oa 0
681 lappend vupptr
($v) $oa
682 lappend vleftptr
($v) 0
683 lappend vbackptr
($v) 0
684 for {set b
[lindex
$vdownptr($v) $na]} {$b != 0} {set b
[lindex
$vleftptr($v) $b]} {
685 lset vupptr
($v) $b $na
689 proc renumbervarc
{a v
} {
690 global parents children varctok varcstart varccommits
691 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
693 set t1
[clock clicks
-milliseconds]
699 if {[info exists isrelated
($a)]} {
701 set id
[lindex
$varccommits($v,$a) end
]
702 foreach p
$parents($v,$id) {
703 if {[info exists varcid
($v,$p)]} {
704 set isrelated
($varcid($v,$p)) 1
709 set b
[lindex
$vdownptr($v) $a]
712 set b
[lindex
$vleftptr($v) $a]
714 set a
[lindex
$vupptr($v) $a]
720 if {![info exists kidchanged
($a)]} continue
721 set id
[lindex
$varcstart($v) $a]
722 if {[llength
$children($v,$id)] > 1} {
723 set children
($v,$id) [lsort
-command [list vtokcmp
$v] \
726 set oldtok
[lindex
$varctok($v) $a]
727 if {!$vdatemode($v)} {
733 set kid
[last_real_child
$v,$id]
735 set k
$varcid($v,$kid)
736 if {[string compare
[lindex
$varctok($v) $k] $tok] > 0} {
739 set tok
[lindex
$varctok($v) $k]
743 set i
[lsearch
-exact $parents($v,$ki) $id]
744 set j
[expr {[llength
$parents($v,$ki)] - 1 - $i}]
745 append tok
[strrep
$j]
747 if {$tok eq
$oldtok} {
750 set id
[lindex
$varccommits($v,$a) end
]
751 foreach p
$parents($v,$id) {
752 if {[info exists varcid
($v,$p)]} {
753 set kidchanged
($varcid($v,$p)) 1
758 lset varctok
($v) $a $tok
759 set b
[lindex
$vupptr($v) $a]
761 if {[string compare
[lindex
$varctok($v) $ka] $vtokmod($v)] < 0} {
764 if {[string compare
[lindex
$varctok($v) $b] $vtokmod($v)] < 0} {
767 set c
[lindex
$vbackptr($v) $a]
768 set d
[lindex
$vleftptr($v) $a]
770 lset vdownptr
($v) $b $d
772 lset vleftptr
($v) $c $d
775 lset vbackptr
($v) $d $c
777 if {[lindex
$vlastins($v) $b] == $a} {
778 lset vlastins
($v) $b $c
780 lset vupptr
($v) $a $ka
781 set c
[lindex
$vlastins($v) $ka]
783 [string compare
$tok [lindex
$varctok($v) $c]] < 0} {
785 set b
[lindex
$vdownptr($v) $ka]
787 set b
[lindex
$vleftptr($v) $c]
790 [string compare
$tok [lindex
$varctok($v) $b]] >= 0} {
792 set b
[lindex
$vleftptr($v) $c]
795 lset vdownptr
($v) $ka $a
796 lset vbackptr
($v) $a 0
798 lset vleftptr
($v) $c $a
799 lset vbackptr
($v) $a $c
801 lset vleftptr
($v) $a $b
803 lset vbackptr
($v) $b $a
805 lset vlastins
($v) $ka $a
808 foreach id
[array names sortkids
] {
809 if {[llength
$children($v,$id)] > 1} {
810 set children
($v,$id) [lsort
-command [list vtokcmp
$v] \
814 set t2
[clock clicks
-milliseconds]
815 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
818 # Fix up the graph after we have found out that in view $v,
819 # $p (a commit that we have already seen) is actually the parent
820 # of the last commit in arc $a.
821 proc fix_reversal
{p a v
} {
822 global varcid varcstart varctok vupptr
824 set pa
$varcid($v,$p)
825 if {$p ne
[lindex
$varcstart($v) $pa]} {
827 set pa
$varcid($v,$p)
829 # seeds always need to be renumbered
830 if {[lindex
$vupptr($v) $pa] == 0 ||
831 [string compare
[lindex
$varctok($v) $a] \
832 [lindex
$varctok($v) $pa]] > 0} {
837 proc insertrow
{id p v
} {
838 global cmitlisted children parents varcid varctok vtokmod
839 global varccommits ordertok commitidx numcommits curview
840 global targetid targetrow
844 set cmitlisted
($vid) 1
845 set children
($vid) {}
846 set parents
($vid) [list
$p]
847 set a
[newvarc
$v $id]
849 if {[string compare
[lindex
$varctok($v) $a] $vtokmod($v)] < 0} {
852 lappend varccommits
($v,$a) $id
854 if {[llength
[lappend children
($vp) $id]] > 1} {
855 set children
($vp) [lsort
-command [list vtokcmp
$v] $children($vp)]
856 catch
{unset ordertok
}
858 fix_reversal
$p $a $v
860 if {$v == $curview} {
861 set numcommits
$commitidx($v)
863 if {[info exists targetid
]} {
864 if {![comes_before
$targetid $p]} {
871 proc insertfakerow
{id p
} {
872 global varcid varccommits parents children cmitlisted
873 global commitidx varctok vtokmod targetid targetrow curview numcommits
877 set i
[lsearch
-exact $varccommits($v,$a) $p]
879 puts
"oops: insertfakerow can't find [shortids $p] on arc $a"
882 set children
($v,$id) {}
883 set parents
($v,$id) [list
$p]
884 set varcid
($v,$id) $a
885 lappend children
($v,$p) $id
886 set cmitlisted
($v,$id) 1
887 set numcommits
[incr commitidx
($v)]
888 # note we deliberately don't update varcstart($v) even if $i == 0
889 set varccommits
($v,$a) [linsert
$varccommits($v,$a) $i $id]
891 if {[info exists targetid
]} {
892 if {![comes_before
$targetid $p]} {
900 proc removefakerow
{id
} {
901 global varcid varccommits parents children commitidx
902 global varctok vtokmod cmitlisted currentid selectedline
903 global targetid curview numcommits
906 if {[llength
$parents($v,$id)] != 1} {
907 puts
"oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
910 set p
[lindex
$parents($v,$id) 0]
911 set a
$varcid($v,$id)
912 set i
[lsearch
-exact $varccommits($v,$a) $id]
914 puts
"oops: removefakerow can't find [shortids $id] on arc $a"
918 set varccommits
($v,$a) [lreplace
$varccommits($v,$a) $i $i]
919 unset parents
($v,$id)
920 unset children
($v,$id)
921 unset cmitlisted
($v,$id)
922 set numcommits
[incr commitidx
($v) -1]
923 set j
[lsearch
-exact $children($v,$p) $id]
925 set children
($v,$p) [lreplace
$children($v,$p) $j $j]
928 if {[info exist currentid
] && $id eq
$currentid} {
932 if {[info exists targetid
] && $targetid eq
$id} {
939 proc first_real_child
{vp
} {
940 global children nullid nullid2
942 foreach id
$children($vp) {
943 if {$id ne
$nullid && $id ne
$nullid2} {
950 proc last_real_child
{vp
} {
951 global children nullid nullid2
953 set kids
$children($vp)
954 for {set i
[llength
$kids]} {[incr i
-1] >= 0} {} {
955 set id
[lindex
$kids $i]
956 if {$id ne
$nullid && $id ne
$nullid2} {
963 proc vtokcmp
{v a b
} {
964 global varctok varcid
966 return [string compare
[lindex
$varctok($v) $varcid($v,$a)] \
967 [lindex
$varctok($v) $varcid($v,$b)]]
970 # This assumes that if lim is not given, the caller has checked that
971 # arc a's token is less than $vtokmod($v)
972 proc modify_arc
{v a
{lim
{}}} {
973 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
976 set c
[string compare
[lindex
$varctok($v) $a] $vtokmod($v)]
979 set r
[lindex
$varcrow($v) $a]
980 if {$r ne
{} && $vrowmod($v) <= $r + $lim} return
983 set vtokmod
($v) [lindex
$varctok($v) $a]
985 if {$v == $curview} {
986 while {$a != 0 && [lindex
$varcrow($v) $a] eq
{}} {
987 set a
[lindex
$vupptr($v) $a]
993 set lim
[llength
$varccommits($v,$a)]
995 set r
[expr {[lindex
$varcrow($v) $a] + $lim}]
1002 proc update_arcrows
{v
} {
1003 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1004 global varcid vrownum varcorder varcix varccommits
1005 global vupptr vdownptr vleftptr varctok
1006 global displayorder parentlist curview cached_commitrow
1008 if {$vrowmod($v) == $commitidx($v)} return
1009 if {$v == $curview} {
1010 if {[llength
$displayorder] > $vrowmod($v)} {
1011 set displayorder
[lrange
$displayorder 0 [expr {$vrowmod($v) - 1}]]
1012 set parentlist
[lrange
$parentlist 0 [expr {$vrowmod($v) - 1}]]
1014 catch
{unset cached_commitrow
}
1016 set narctot
[expr {[llength
$varctok($v)] - 1}]
1018 while {$a != 0 && [lindex
$varcix($v) $a] eq
{}} {
1019 # go up the tree until we find something that has a row number,
1020 # or we get to a seed
1021 set a
[lindex
$vupptr($v) $a]
1024 set a
[lindex
$vdownptr($v) 0]
1027 set varcorder
($v) [list
$a]
1028 lset varcix
($v) $a 0
1029 lset varcrow
($v) $a 0
1033 set arcn
[lindex
$varcix($v) $a]
1034 if {[llength
$vrownum($v)] > $arcn + 1} {
1035 set vrownum
($v) [lrange
$vrownum($v) 0 $arcn]
1036 set varcorder
($v) [lrange
$varcorder($v) 0 $arcn]
1038 set row
[lindex
$varcrow($v) $a]
1042 incr row
[llength
$varccommits($v,$a)]
1043 # go down if possible
1044 set b
[lindex
$vdownptr($v) $a]
1046 # if not, go left, or go up until we can go left
1048 set b
[lindex
$vleftptr($v) $a]
1050 set a
[lindex
$vupptr($v) $a]
1056 lappend vrownum
($v) $row
1057 lappend varcorder
($v) $a
1058 lset varcix
($v) $a $arcn
1059 lset varcrow
($v) $a $row
1061 set vtokmod
($v) [lindex
$varctok($v) $p]
1063 set vrowmod
($v) $row
1064 if {[info exists currentid
]} {
1065 set selectedline
[rowofcommit
$currentid]
1069 # Test whether view $v contains commit $id
1070 proc commitinview
{id v
} {
1073 return [info exists varcid
($v,$id)]
1076 # Return the row number for commit $id in the current view
1077 proc rowofcommit
{id
} {
1078 global varcid varccommits varcrow curview cached_commitrow
1079 global varctok vtokmod
1082 if {![info exists varcid
($v,$id)]} {
1083 puts
"oops rowofcommit no arc for [shortids $id]"
1086 set a
$varcid($v,$id)
1087 if {[string compare
[lindex
$varctok($v) $a] $vtokmod($v)] >= 0} {
1090 if {[info exists cached_commitrow
($id)]} {
1091 return $cached_commitrow($id)
1093 set i
[lsearch
-exact $varccommits($v,$a) $id]
1095 puts
"oops didn't find commit [shortids $id] in arc $a"
1098 incr i
[lindex
$varcrow($v) $a]
1099 set cached_commitrow
($id) $i
1103 # Returns 1 if a is on an earlier row than b, otherwise 0
1104 proc comes_before
{a b
} {
1105 global varcid varctok curview
1108 if {$a eq
$b ||
![info exists varcid
($v,$a)] || \
1109 ![info exists varcid
($v,$b)]} {
1112 if {$varcid($v,$a) != $varcid($v,$b)} {
1113 return [expr {[string compare
[lindex
$varctok($v) $varcid($v,$a)] \
1114 [lindex
$varctok($v) $varcid($v,$b)]] < 0}]
1116 return [expr {[rowofcommit
$a] < [rowofcommit
$b]}]
1119 proc bsearch
{l elt
} {
1120 if {[llength
$l] == 0 ||
$elt <= [lindex
$l 0]} {
1125 while {$hi - $lo > 1} {
1126 set mid
[expr {int
(($lo + $hi) / 2)}]
1127 set t
[lindex
$l $mid]
1130 } elseif
{$elt > $t} {
1139 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1140 proc make_disporder
{start end
} {
1141 global vrownum curview commitidx displayorder parentlist
1142 global varccommits varcorder parents vrowmod varcrow
1143 global d_valid_start d_valid_end
1145 if {$end > $vrowmod($curview)} {
1146 update_arcrows
$curview
1148 set ai
[bsearch
$vrownum($curview) $start]
1149 set start
[lindex
$vrownum($curview) $ai]
1150 set narc
[llength
$vrownum($curview)]
1151 for {set r
$start} {$ai < $narc && $r < $end} {incr ai
} {
1152 set a
[lindex
$varcorder($curview) $ai]
1153 set l
[llength
$displayorder]
1154 set al
[llength
$varccommits($curview,$a)]
1155 if {$l < $r + $al} {
1157 set pad
[ntimes
[expr {$r - $l}] {}]
1158 set displayorder
[concat
$displayorder $pad]
1159 set parentlist
[concat
$parentlist $pad]
1160 } elseif
{$l > $r} {
1161 set displayorder
[lrange
$displayorder 0 [expr {$r - 1}]]
1162 set parentlist
[lrange
$parentlist 0 [expr {$r - 1}]]
1164 foreach id
$varccommits($curview,$a) {
1165 lappend displayorder
$id
1166 lappend parentlist
$parents($curview,$id)
1168 } elseif
{[lindex
$displayorder [expr {$r + $al - 1}]] eq
{}} {
1170 foreach id
$varccommits($curview,$a) {
1171 lset displayorder
$i $id
1172 lset parentlist
$i $parents($curview,$id)
1180 proc commitonrow
{row
} {
1183 set id
[lindex
$displayorder $row]
1185 make_disporder
$row [expr {$row + 1}]
1186 set id
[lindex
$displayorder $row]
1191 proc closevarcs
{v
} {
1192 global varctok varccommits varcid parents children
1193 global cmitlisted commitidx commitinterest vtokmod
1195 set missing_parents
0
1197 set narcs
[llength
$varctok($v)]
1198 for {set a
1} {$a < $narcs} {incr a
} {
1199 set id
[lindex
$varccommits($v,$a) end
]
1200 foreach p
$parents($v,$id) {
1201 if {[info exists varcid
($v,$p)]} continue
1202 # add p as a new commit
1203 incr missing_parents
1204 set cmitlisted
($v,$p) 0
1205 set parents
($v,$p) {}
1206 if {[llength
$children($v,$p)] == 1 &&
1207 [llength
$parents($v,$id)] == 1} {
1210 set b
[newvarc
$v $p]
1212 set varcid
($v,$p) $b
1213 if {[string compare
[lindex
$varctok($v) $b] $vtokmod($v)] < 0} {
1216 lappend varccommits
($v,$b) $p
1218 if {[info exists commitinterest
($p)]} {
1219 foreach
script $commitinterest($p) {
1220 lappend scripts
[string map
[list
"%I" $p] $script]
1222 unset commitinterest
($id)
1226 if {$missing_parents > 0} {
1227 foreach s
$scripts {
1233 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1234 # Assumes we already have an arc for $rwid.
1235 proc rewrite_commit
{v id rwid
} {
1236 global children parents varcid varctok vtokmod varccommits
1238 foreach ch
$children($v,$id) {
1239 # make $rwid be $ch's parent in place of $id
1240 set i
[lsearch
-exact $parents($v,$ch) $id]
1242 puts
"oops rewrite_commit didn't find $id in parent list for $ch"
1244 set parents
($v,$ch) [lreplace
$parents($v,$ch) $i $i $rwid]
1245 # add $ch to $rwid's children and sort the list if necessary
1246 if {[llength
[lappend children
($v,$rwid) $ch]] > 1} {
1247 set children
($v,$rwid) [lsort
-command [list vtokcmp
$v] \
1248 $children($v,$rwid)]
1250 # fix the graph after joining $id to $rwid
1251 set a
$varcid($v,$ch)
1252 fix_reversal
$rwid $a $v
1253 # parentlist is wrong for the last element of arc $a
1254 # even if displayorder is right, hence the 3rd arg here
1255 modify_arc
$v $a [expr {[llength
$varccommits($v,$a)] - 1}]
1259 proc getcommitlines
{fd inst view updating
} {
1260 global cmitlisted commitinterest leftover
1261 global commitidx commitdata vdatemode
1262 global parents children curview hlview
1263 global idpending ordertok
1264 global varccommits varcid varctok vtokmod vfilelimit
1266 set stuff
[read $fd 500000]
1267 # git log doesn't terminate the last commit with a null...
1268 if {$stuff == {} && $leftover($inst) ne
{} && [eof
$fd]} {
1275 global commfd viewcomplete viewactive viewname
1276 global viewinstances
1278 set i
[lsearch
-exact $viewinstances($view) $inst]
1280 set viewinstances
($view) [lreplace
$viewinstances($view) $i $i]
1282 # set it blocking so we wait for the process to terminate
1283 fconfigure
$fd -blocking 1
1284 if {[catch
{close
$fd} err
]} {
1286 if {$view != $curview} {
1287 set fv
" for the \"$viewname($view)\" view"
1289 if {[string range
$err 0 4] == "usage"} {
1290 set err
"Gitk: error reading commits$fv:\
1291 bad arguments to git log."
1292 if {$viewname($view) eq
"Command line"} {
1294 " (Note: arguments to gitk are passed to git log\
1295 to allow selection of commits to be displayed.)"
1298 set err
"Error reading commits$fv: $err"
1302 if {[incr viewactive
($view) -1] <= 0} {
1303 set viewcomplete
($view) 1
1304 # Check if we have seen any ids listed as parents that haven't
1305 # appeared in the list
1309 if {$view == $curview} {
1318 set i
[string first
"\0" $stuff $start]
1320 append leftover
($inst) [string range
$stuff $start end
]
1324 set cmit
$leftover($inst)
1325 append cmit
[string range
$stuff 0 [expr {$i - 1}]]
1326 set leftover
($inst) {}
1328 set cmit
[string range
$stuff $start [expr {$i - 1}]]
1330 set start
[expr {$i + 1}]
1331 set j
[string first
"\n" $cmit]
1334 if {$j >= 0 && [string match
"commit *" $cmit]} {
1335 set ids
[string range
$cmit 7 [expr {$j - 1}]]
1336 if {[string match
{[-^
<>]*} $ids]} {
1337 switch
-- [string index
$ids 0] {
1343 set ids
[string range
$ids 1 end
]
1347 if {[string length
$id] != 40} {
1355 if {[string length
$shortcmit] > 80} {
1356 set shortcmit
"[string range $shortcmit 0 80]..."
1358 error_popup
"[mc "Can
't parse git log output:"] {$shortcmit}"
1361 set id [lindex $ids 0]
1364 if {!$listed && $updating && ![info exists varcid($vid)] &&
1365 $vfilelimit($view) ne {}} {
1366 # git log doesn't rewrite parents
for unlisted commits
1367 # when doing path limiting, so work around that here
1368 # by working out the rewritten parent with git rev-list
1369 # and if we already know about it, using the rewritten
1370 # parent as a substitute parent for $id's children.
1372 set rwid
[exec git rev-list
--first-parent --max-count=1 \
1373 $id -- $vfilelimit($view)]
1375 if {$rwid ne
{} && [info exists varcid
($view,$rwid)]} {
1376 # use $rwid in place of $id
1377 rewrite_commit
$view $id $rwid
1384 if {[info exists varcid
($vid)]} {
1385 if {$cmitlisted($vid) ||
!$listed} continue
1389 set olds
[lrange
$ids 1 end
]
1393 set commitdata
($id) [string range
$cmit [expr {$j + 1}] end
]
1394 set cmitlisted
($vid) $listed
1395 set parents
($vid) $olds
1396 if {![info exists children
($vid)]} {
1397 set children
($vid) {}
1398 } elseif
{$a == 0 && [llength
$children($vid)] == 1} {
1399 set k
[lindex
$children($vid) 0]
1400 if {[llength
$parents($view,$k)] == 1 &&
1401 (!$vdatemode($view) ||
1402 $varcid($view,$k) == [llength
$varctok($view)] - 1)} {
1403 set a
$varcid($view,$k)
1408 set a
[newvarc
$view $id]
1410 if {[string compare
[lindex
$varctok($view) $a] $vtokmod($view)] < 0} {
1413 if {![info exists varcid
($vid)]} {
1415 lappend varccommits
($view,$a) $id
1416 incr commitidx
($view)
1421 if {$i == 0 ||
[lsearch
-exact $olds $p] >= $i} {
1423 if {[llength
[lappend children
($vp) $id]] > 1 &&
1424 [vtokcmp
$view [lindex
$children($vp) end-1
] $id] > 0} {
1425 set children
($vp) [lsort
-command [list vtokcmp
$view] \
1427 catch
{unset ordertok
}
1429 if {[info exists varcid
($view,$p)]} {
1430 fix_reversal
$p $a $view
1436 if {[info exists commitinterest
($id)]} {
1437 foreach
script $commitinterest($id) {
1438 lappend scripts
[string map
[list
"%I" $id] $script]
1440 unset commitinterest
($id)
1445 global numcommits hlview
1447 if {$view == $curview} {
1448 set numcommits
$commitidx($view)
1451 if {[info exists hlview
] && $view == $hlview} {
1452 # we never actually get here...
1455 foreach s
$scripts {
1462 proc chewcommits
{} {
1463 global curview hlview viewcomplete
1464 global pending_select
1467 if {$viewcomplete($curview)} {
1468 global commitidx varctok
1469 global numcommits startmsecs
1470 global mainheadid nullid
1472 if {[info exists pending_select
]} {
1473 set row
[first_real_row
]
1476 if {$commitidx($curview) > 0} {
1477 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1478 #puts "overall $ms ms for $numcommits commits"
1479 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1481 show_status
[mc
"No commits selected"]
1488 proc readcommit
{id
} {
1489 if {[catch
{set contents
[exec git cat-file commit
$id]}]} return
1490 parsecommit
$id $contents 0
1493 proc parsecommit
{id contents listed
} {
1494 global commitinfo cdate
1503 set hdrend
[string first
"\n\n" $contents]
1505 # should never happen...
1506 set hdrend
[string length
$contents]
1508 set header
[string range
$contents 0 [expr {$hdrend - 1}]]
1509 set comment
[string range
$contents [expr {$hdrend + 2}] end
]
1510 foreach line
[split $header "\n"] {
1511 set tag
[lindex
$line 0]
1512 if {$tag == "author"} {
1513 set audate
[lindex
$line end-1
]
1514 set auname
[lrange
$line 1 end-2
]
1515 } elseif
{$tag == "committer"} {
1516 set comdate
[lindex
$line end-1
]
1517 set comname
[lrange
$line 1 end-2
]
1521 # take the first non-blank line of the comment as the headline
1522 set headline
[string trimleft
$comment]
1523 set i
[string first
"\n" $headline]
1525 set headline
[string range
$headline 0 $i]
1527 set headline
[string trimright
$headline]
1528 set i
[string first
"\r" $headline]
1530 set headline
[string trimright
[string range
$headline 0 $i]]
1533 # git log indents the comment by 4 spaces;
1534 # if we got this via git cat-file, add the indentation
1536 foreach line
[split $comment "\n"] {
1537 append newcomment
" "
1538 append newcomment
$line
1539 append newcomment
"\n"
1541 set comment
$newcomment
1543 if {$comdate != {}} {
1544 set cdate
($id) $comdate
1546 set commitinfo
($id) [list
$headline $auname $audate \
1547 $comname $comdate $comment]
1550 proc getcommit
{id
} {
1551 global commitdata commitinfo
1553 if {[info exists commitdata
($id)]} {
1554 parsecommit
$id $commitdata($id) 1
1557 if {![info exists commitinfo
($id)]} {
1558 set commitinfo
($id) [list
[mc
"No commit information available"]]
1565 global tagids idtags headids idheads tagobjid
1566 global otherrefids idotherrefs mainhead mainheadid
1568 foreach v
{tagids idtags headids idheads otherrefids idotherrefs
} {
1571 set refd
[open
[list | git show-ref
-d] r
]
1572 while {[gets
$refd line
] >= 0} {
1573 if {[string index
$line 40] ne
" "} continue
1574 set id
[string range
$line 0 39]
1575 set ref
[string range
$line 41 end
]
1576 if {![string match
"refs/*" $ref]} continue
1577 set name
[string range
$ref 5 end
]
1578 if {[string match
"remotes/*" $name]} {
1579 if {![string match
"*/HEAD" $name]} {
1580 set headids
($name) $id
1581 lappend idheads
($id) $name
1583 } elseif
{[string match
"heads/*" $name]} {
1584 set name
[string range
$name 6 end
]
1585 set headids
($name) $id
1586 lappend idheads
($id) $name
1587 } elseif
{[string match
"tags/*" $name]} {
1588 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1589 # which is what we want since the former is the commit ID
1590 set name
[string range
$name 5 end
]
1591 if {[string match
"*^{}" $name]} {
1592 set name
[string range
$name 0 end-3
]
1594 set tagobjid
($name) $id
1596 set tagids
($name) $id
1597 lappend idtags
($id) $name
1599 set otherrefids
($name) $id
1600 lappend idotherrefs
($id) $name
1607 set thehead
[exec git symbolic-ref HEAD
]
1608 if {[string match
"refs/heads/*" $thehead]} {
1609 set mainhead
[string range
$thehead 11 end
]
1610 if {[info exists headids
($mainhead)]} {
1611 set mainheadid
$headids($mainhead)
1617 # skip over fake commits
1618 proc first_real_row
{} {
1619 global nullid nullid2 numcommits
1621 for {set row
0} {$row < $numcommits} {incr row
} {
1622 set id
[commitonrow
$row]
1623 if {$id ne
$nullid && $id ne
$nullid2} {
1630 # update things for a head moved to a child of its previous location
1631 proc movehead
{id name
} {
1632 global headids idheads
1634 removehead
$headids($name) $name
1635 set headids
($name) $id
1636 lappend idheads
($id) $name
1639 # update things when a head has been removed
1640 proc removehead
{id name
} {
1641 global headids idheads
1643 if {$idheads($id) eq
$name} {
1646 set i
[lsearch
-exact $idheads($id) $name]
1648 set idheads
($id) [lreplace
$idheads($id) $i $i]
1651 unset headids
($name)
1654 proc show_error
{w top msg
} {
1655 message
$w.m
-text $msg -justify center
-aspect 400
1656 pack
$w.m
-side top
-fill x
-padx 20 -pady 20
1657 button
$w.ok
-text [mc OK
] -command "destroy $top"
1658 pack
$w.ok
-side bottom
-fill x
1659 bind $top <Visibility
> "grab $top; focus $top"
1660 bind $top <Key-Return
> "destroy $top"
1664 proc error_popup msg
{
1668 show_error
$w $w $msg
1671 proc confirm_popup msg
{
1677 message
$w.m
-text $msg -justify center
-aspect 400
1678 pack
$w.m
-side top
-fill x
-padx 20 -pady 20
1679 button
$w.ok
-text [mc OK
] -command "set confirm_ok 1; destroy $w"
1680 pack
$w.ok
-side left
-fill x
1681 button
$w.cancel
-text [mc Cancel
] -command "destroy $w"
1682 pack
$w.cancel
-side right
-fill x
1683 bind $w <Visibility
> "grab $w; focus $w"
1688 proc setoptions
{} {
1689 option add
*Panedwindow.showHandle
1 startupFile
1690 option add
*Panedwindow.sashRelief raised startupFile
1691 option add
*Button.font uifont startupFile
1692 option add
*Checkbutton.font uifont startupFile
1693 option add
*Radiobutton.font uifont startupFile
1694 option add
*Menu.font uifont startupFile
1695 option add
*Menubutton.font uifont startupFile
1696 option add
*Label.font uifont startupFile
1697 option add
*Message.font uifont startupFile
1698 option add
*Entry.font uifont startupFile
1701 proc makewindow
{} {
1702 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1704 global findtype findtypemenu findloc findstring fstring geometry
1705 global entries sha1entry sha1string sha1but
1706 global diffcontextstring diffcontext
1708 global maincursor textcursor curtextcursor
1709 global rowctxmenu fakerowmenu mergemax wrapcomment
1710 global highlight_files gdttype
1711 global searchstring sstring
1712 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1713 global headctxmenu progresscanv progressitem progresscoords statusw
1714 global fprogitem fprogcoord lastprogupdate progupdatepending
1715 global rprogitem rprogcoord rownumsel numcommits
1719 .bar add cascade
-label [mc
"File"] -menu .bar.
file
1721 .bar.
file add
command -label [mc
"Update"] -command updatecommits
1722 .bar.
file add
command -label [mc
"Reload"] -command reloadcommits
1723 .bar.
file add
command -label [mc
"Reread references"] -command rereadrefs
1724 .bar.
file add
command -label [mc
"List references"] -command showrefs
1725 .bar.
file add
command -label [mc
"Quit"] -command doquit
1727 .bar add cascade
-label [mc
"Edit"] -menu .bar.edit
1728 .bar.edit add
command -label [mc
"Preferences"] -command doprefs
1731 .bar add cascade
-label [mc
"View"] -menu .bar.view
1732 .bar.view add
command -label [mc
"New view..."] -command {newview
0}
1733 .bar.view add
command -label [mc
"Edit view..."] -command editview \
1735 .bar.view add
command -label [mc
"Delete view"] -command delview
-state disabled
1736 .bar.view add separator
1737 .bar.view add radiobutton
-label [mc
"All files"] -command {showview
0} \
1738 -variable selectedview
-value 0
1741 .bar add cascade
-label [mc
"Help"] -menu .bar.
help
1742 .bar.
help add
command -label [mc
"About gitk"] -command about
1743 .bar.
help add
command -label [mc
"Key bindings"] -command keys
1745 . configure
-menu .bar
1747 # the gui has upper and lower half, parts of a paned window.
1748 panedwindow .ctop
-orient vertical
1750 # possibly use assumed geometry
1751 if {![info exists geometry
(pwsash0
)]} {
1752 set geometry
(topheight
) [expr {15 * $linespc}]
1753 set geometry
(topwidth
) [expr {80 * $charspc}]
1754 set geometry
(botheight
) [expr {15 * $linespc}]
1755 set geometry
(botwidth
) [expr {50 * $charspc}]
1756 set geometry
(pwsash0
) "[expr {40 * $charspc}] 2"
1757 set geometry
(pwsash1
) "[expr {60 * $charspc}] 2"
1760 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1761 frame .tf
-height $geometry(topheight
) -width $geometry(topwidth
)
1763 panedwindow .tf.histframe.pwclist
-orient horizontal
-sashpad 0 -handlesize 4
1765 # create three canvases
1766 set cscroll .tf.histframe.csb
1767 set canv .tf.histframe.pwclist.canv
1769 -selectbackground $selectbgcolor \
1770 -background $bgcolor -bd 0 \
1771 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1772 .tf.histframe.pwclist add
$canv
1773 set canv2 .tf.histframe.pwclist.canv2
1775 -selectbackground $selectbgcolor \
1776 -background $bgcolor -bd 0 -yscrollincr $linespc
1777 .tf.histframe.pwclist add
$canv2
1778 set canv3 .tf.histframe.pwclist.canv3
1780 -selectbackground $selectbgcolor \
1781 -background $bgcolor -bd 0 -yscrollincr $linespc
1782 .tf.histframe.pwclist add
$canv3
1783 eval .tf.histframe.pwclist sash place
0 $geometry(pwsash0
)
1784 eval .tf.histframe.pwclist sash place
1 $geometry(pwsash1
)
1786 # a scroll bar to rule them
1787 scrollbar
$cscroll -command {allcanvs yview
} -highlightthickness 0
1788 pack
$cscroll -side right
-fill y
1789 bind .tf.histframe.pwclist
<Configure
> {resizeclistpanes
%W
%w
}
1790 lappend bglist
$canv $canv2 $canv3
1791 pack .tf.histframe.pwclist
-fill both
-expand 1 -side left
1793 # we have two button bars at bottom of top frame. Bar 1
1795 frame .tf.lbar
-height 15
1797 set sha1entry .tf.bar.sha1
1798 set entries
$sha1entry
1799 set sha1but .tf.bar.sha1label
1800 button
$sha1but -text [mc
"SHA1 ID: "] -state disabled
-relief flat \
1801 -command gotocommit
-width 8
1802 $sha1but conf
-disabledforeground [$sha1but cget
-foreground]
1803 pack .tf.bar.sha1label
-side left
1804 entry
$sha1entry -width 40 -font textfont
-textvariable sha1string
1805 trace add variable sha1string
write sha1change
1806 pack
$sha1entry -side left
-pady 2
1808 image create bitmap bm-left
-data {
1809 #define left_width 16
1810 #define left_height 16
1811 static unsigned char left_bits
[] = {
1812 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1813 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1814 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1816 image create bitmap bm-right
-data {
1817 #define right_width 16
1818 #define right_height 16
1819 static unsigned char right_bits
[] = {
1820 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1821 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1822 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1824 button .tf.bar.leftbut
-image bm-left
-command goback \
1825 -state disabled
-width 26
1826 pack .tf.bar.leftbut
-side left
-fill y
1827 button .tf.bar.rightbut
-image bm-right
-command goforw \
1828 -state disabled
-width 26
1829 pack .tf.bar.rightbut
-side left
-fill y
1831 label .tf.bar.rowlabel
-text [mc
"Row"]
1833 label .tf.bar.rownum
-width 7 -font textfont
-textvariable rownumsel \
1834 -relief sunken
-anchor e
1835 label .tf.bar.rowlabel2
-text "/"
1836 label .tf.bar.numcommits
-width 7 -font textfont
-textvariable numcommits \
1837 -relief sunken
-anchor e
1838 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
1841 trace add variable selectedline
{write unset} selectedline_change
1843 # Status label and progress bar
1844 set statusw .tf.bar.status
1845 label
$statusw -width 15 -relief sunken
1846 pack
$statusw -side left
-padx 5
1847 set h
[expr {[font metrics uifont
-linespace] + 2}]
1848 set progresscanv .tf.bar.progress
1849 canvas
$progresscanv -relief sunken
-height $h -borderwidth 2
1850 set progressitem
[$progresscanv create rect
-1 0 0 $h -fill green
]
1851 set fprogitem
[$progresscanv create rect
-1 0 0 $h -fill yellow
]
1852 set rprogitem
[$progresscanv create rect
-1 0 0 $h -fill red
]
1853 pack
$progresscanv -side right
-expand 1 -fill x
1854 set progresscoords
{0 0}
1857 bind $progresscanv <Configure
> adjustprogress
1858 set lastprogupdate
[clock clicks
-milliseconds]
1859 set progupdatepending
0
1861 # build up the bottom bar of upper window
1862 label .tf.lbar.flabel
-text "[mc "Find
"] "
1863 button .tf.lbar.fnext
-text [mc
"next"] -command {dofind
1 1}
1864 button .tf.lbar.fprev
-text [mc
"prev"] -command {dofind
-1 1}
1865 label .tf.lbar.flab2
-text " [mc "commit
"] "
1866 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
1868 set gdttype
[mc
"containing:"]
1869 set gm
[tk_optionMenu .tf.lbar.gdttype gdttype \
1870 [mc
"containing:"] \
1871 [mc
"touching paths:"] \
1872 [mc
"adding/removing string:"]]
1873 trace add variable gdttype
write gdttype_change
1874 pack .tf.lbar.gdttype
-side left
-fill y
1877 set fstring .tf.lbar.findstring
1878 lappend entries
$fstring
1879 entry
$fstring -width 30 -font textfont
-textvariable findstring
1880 trace add variable findstring
write find_change
1881 set findtype
[mc
"Exact"]
1882 set findtypemenu
[tk_optionMenu .tf.lbar.findtype \
1883 findtype
[mc
"Exact"] [mc
"IgnCase"] [mc
"Regexp"]]
1884 trace add variable findtype
write findcom_change
1885 set findloc
[mc
"All fields"]
1886 tk_optionMenu .tf.lbar.findloc findloc
[mc
"All fields"] [mc
"Headline"] \
1887 [mc
"Comments"] [mc
"Author"] [mc
"Committer"]
1888 trace add variable findloc
write find_change
1889 pack .tf.lbar.findloc
-side right
1890 pack .tf.lbar.findtype
-side right
1891 pack
$fstring -side left
-expand 1 -fill x
1893 # Finish putting the upper half of the viewer together
1894 pack .tf.lbar
-in .tf
-side bottom
-fill x
1895 pack .tf.bar
-in .tf
-side bottom
-fill x
1896 pack .tf.histframe
-fill both
-side top
-expand 1
1898 .ctop paneconfigure .tf
-height $geometry(topheight
)
1899 .ctop paneconfigure .tf
-width $geometry(topwidth
)
1901 # now build up the bottom
1902 panedwindow .pwbottom
-orient horizontal
1904 # lower left, a text box over search bar, scroll bar to the right
1905 # if we know window height, then that will set the lower text height, otherwise
1906 # we set lower text height which will drive window height
1907 if {[info exists geometry
(main
)]} {
1908 frame .bleft
-width $geometry(botwidth
)
1910 frame .bleft
-width $geometry(botwidth
) -height $geometry(botheight
)
1916 button .bleft.top.search
-text [mc
"Search"] -command dosearch
1917 pack .bleft.top.search
-side left
-padx 5
1918 set sstring .bleft.top.sstring
1919 entry
$sstring -width 20 -font textfont
-textvariable searchstring
1920 lappend entries
$sstring
1921 trace add variable searchstring
write incrsearch
1922 pack
$sstring -side left
-expand 1 -fill x
1923 radiobutton .bleft.mid.
diff -text [mc
"Diff"] \
1924 -command changediffdisp
-variable diffelide
-value {0 0}
1925 radiobutton .bleft.mid.old
-text [mc
"Old version"] \
1926 -command changediffdisp
-variable diffelide
-value {0 1}
1927 radiobutton .bleft.mid.new
-text [mc
"New version"] \
1928 -command changediffdisp
-variable diffelide
-value {1 0}
1929 label .bleft.mid.labeldiffcontext
-text " [mc "Lines of context
"]: "
1930 pack .bleft.mid.
diff .bleft.mid.old .bleft.mid.new
-side left
1931 spinbox .bleft.mid.diffcontext
-width 5 -font textfont \
1932 -from 1 -increment 1 -to 10000000 \
1933 -validate all
-validatecommand "diffcontextvalidate %P" \
1934 -textvariable diffcontextstring
1935 .bleft.mid.diffcontext
set $diffcontext
1936 trace add variable diffcontextstring
write diffcontextchange
1937 lappend entries .bleft.mid.diffcontext
1938 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext
-side left
1939 checkbutton .bleft.mid.ignspace
-text [mc
"Ignore space change"] \
1940 -command changeignorespace
-variable ignorespace
1941 pack .bleft.mid.ignspace
-side left
-padx 5
1942 set ctext .bleft.bottom.ctext
1943 text
$ctext -background $bgcolor -foreground $fgcolor \
1944 -state disabled
-font textfont \
1945 -yscrollcommand scrolltext
-wrap none \
1946 -xscrollcommand ".bleft.bottom.sbhorizontal set"
1948 $ctext conf
-tabstyle wordprocessor
1950 scrollbar .bleft.bottom.sb
-command "$ctext yview"
1951 scrollbar .bleft.bottom.sbhorizontal
-command "$ctext xview" -orient h \
1953 pack .bleft.top
-side top
-fill x
1954 pack .bleft.mid
-side top
-fill x
1955 grid
$ctext .bleft.bottom.sb
-sticky nsew
1956 grid .bleft.bottom.sbhorizontal
-sticky ew
1957 grid columnconfigure .bleft.bottom
0 -weight 1
1958 grid rowconfigure .bleft.bottom
0 -weight 1
1959 grid rowconfigure .bleft.bottom
1 -weight 0
1960 pack .bleft.bottom
-side top
-fill both
-expand 1
1961 lappend bglist
$ctext
1962 lappend fglist
$ctext
1964 $ctext tag conf comment
-wrap $wrapcomment
1965 $ctext tag conf filesep
-font textfontbold
-back "#aaaaaa"
1966 $ctext tag conf hunksep
-fore [lindex
$diffcolors 2]
1967 $ctext tag conf d0
-fore [lindex
$diffcolors 0]
1968 $ctext tag conf d1
-fore [lindex
$diffcolors 1]
1969 $ctext tag conf m0
-fore red
1970 $ctext tag conf m1
-fore blue
1971 $ctext tag conf m2
-fore green
1972 $ctext tag conf m3
-fore purple
1973 $ctext tag conf
m4 -fore brown
1974 $ctext tag conf m5
-fore "#009090"
1975 $ctext tag conf m6
-fore magenta
1976 $ctext tag conf m7
-fore "#808000"
1977 $ctext tag conf m8
-fore "#009000"
1978 $ctext tag conf m9
-fore "#ff0080"
1979 $ctext tag conf m10
-fore cyan
1980 $ctext tag conf m11
-fore "#b07070"
1981 $ctext tag conf m12
-fore "#70b0f0"
1982 $ctext tag conf m13
-fore "#70f0b0"
1983 $ctext tag conf m14
-fore "#f0b070"
1984 $ctext tag conf m15
-fore "#ff70b0"
1985 $ctext tag conf mmax
-fore darkgrey
1987 $ctext tag conf mresult
-font textfontbold
1988 $ctext tag conf msep
-font textfontbold
1989 $ctext tag conf found
-back yellow
1991 .pwbottom add .bleft
1992 .pwbottom paneconfigure .bleft
-width $geometry(botwidth
)
1997 radiobutton .bright.mode.
patch -text [mc
"Patch"] \
1998 -command reselectline
-variable cmitmode
-value "patch"
1999 radiobutton .bright.mode.tree
-text [mc
"Tree"] \
2000 -command reselectline
-variable cmitmode
-value "tree"
2001 grid .bright.mode.
patch .bright.mode.tree
-sticky ew
2002 pack .bright.mode
-side top
-fill x
2003 set cflist .bright.cfiles
2004 set indent
[font measure mainfont
"nn"]
2006 -selectbackground $selectbgcolor \
2007 -background $bgcolor -foreground $fgcolor \
2009 -tabs [list
$indent [expr {2 * $indent}]] \
2010 -yscrollcommand ".bright.sb set" \
2011 -cursor [. cget
-cursor] \
2012 -spacing1 1 -spacing3 1
2013 lappend bglist
$cflist
2014 lappend fglist
$cflist
2015 scrollbar .bright.sb
-command "$cflist yview"
2016 pack .bright.sb
-side right
-fill y
2017 pack
$cflist -side left
-fill both
-expand 1
2018 $cflist tag configure highlight \
2019 -background [$cflist cget
-selectbackground]
2020 $cflist tag configure bold
-font mainfontbold
2022 .pwbottom add .bright
2025 # restore window width & height if known
2026 if {[info exists geometry
(main
)]} {
2027 if {[scan
$geometry(main
) "%dx%d" w h
] >= 2} {
2028 if {$w > [winfo screenwidth .
]} {
2029 set w
[winfo screenwidth .
]
2031 if {$h > [winfo screenheight .
]} {
2032 set h
[winfo screenheight .
]
2034 wm geometry .
"${w}x$h"
2038 if {[tk windowingsystem
] eq
{aqua
}} {
2044 bind .pwbottom
<Configure
> {resizecdetpanes
%W
%w
}
2045 pack .ctop
-fill both
-expand 1
2046 bindall
<1> {selcanvline
%W
%x
%y
}
2047 #bindall <B1-Motion> {selcanvline %W %x %y}
2048 if {[tk windowingsystem
] == "win32"} {
2049 bind .
<MouseWheel
> { windows_mousewheel_redirector
%W
%X
%Y
%D
}
2050 bind $ctext <MouseWheel
> { windows_mousewheel_redirector
%W
%X
%Y
%D
; break }
2052 bindall
<ButtonRelease-4
> "allcanvs yview scroll -5 units"
2053 bindall
<ButtonRelease-5
> "allcanvs yview scroll 5 units"
2054 if {[tk windowingsystem
] eq
"aqua"} {
2055 bindall
<MouseWheel
> {
2056 set delta
[expr {- (%D
)}]
2057 allcanvs yview scroll
$delta units
2061 bindall
<2> "canvscan mark %W %x %y"
2062 bindall
<B2-Motion
> "canvscan dragto %W %x %y"
2063 bindkey
<Home
> selfirstline
2064 bindkey
<End
> sellastline
2065 bind .
<Key-Up
> "selnextline -1"
2066 bind .
<Key-Down
> "selnextline 1"
2067 bind .
<Shift-Key-Up
> "dofind -1 0"
2068 bind .
<Shift-Key-Down
> "dofind 1 0"
2069 bindkey
<Key-Right
> "goforw"
2070 bindkey
<Key-Left
> "goback"
2071 bind .
<Key-Prior
> "selnextpage -1"
2072 bind .
<Key-Next
> "selnextpage 1"
2073 bind .
<$M1B-Home> "allcanvs yview moveto 0.0"
2074 bind .
<$M1B-End> "allcanvs yview moveto 1.0"
2075 bind .
<$M1B-Key-Up> "allcanvs yview scroll -1 units"
2076 bind .
<$M1B-Key-Down> "allcanvs yview scroll 1 units"
2077 bind .
<$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2078 bind .
<$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2079 bindkey
<Key-Delete
> "$ctext yview scroll -1 pages"
2080 bindkey
<Key-BackSpace
> "$ctext yview scroll -1 pages"
2081 bindkey
<Key-space
> "$ctext yview scroll 1 pages"
2082 bindkey p
"selnextline -1"
2083 bindkey n
"selnextline 1"
2086 bindkey i
"selnextline -1"
2087 bindkey k
"selnextline 1"
2091 bindkey d
"$ctext yview scroll 18 units"
2092 bindkey u
"$ctext yview scroll -18 units"
2093 bindkey
/ {dofind
1 1}
2094 bindkey
<Key-Return
> {dofind
1 1}
2095 bindkey ?
{dofind
-1 1}
2097 bindkey
<F5
> updatecommits
2098 bind .
<$M1B-q> doquit
2099 bind .
<$M1B-f> {dofind
1 1}
2100 bind .
<$M1B-g> {dofind
1 0}
2101 bind .
<$M1B-r> dosearchback
2102 bind .
<$M1B-s> dosearch
2103 bind .
<$M1B-equal> {incrfont
1}
2104 bind .
<$M1B-plus> {incrfont
1}
2105 bind .
<$M1B-KP_Add> {incrfont
1}
2106 bind .
<$M1B-minus> {incrfont
-1}
2107 bind .
<$M1B-KP_Subtract> {incrfont
-1}
2108 wm protocol . WM_DELETE_WINDOW doquit
2109 bind .
<Button-1
> "click %W"
2110 bind $fstring <Key-Return
> {dofind
1 1}
2111 bind $sha1entry <Key-Return
> gotocommit
2112 bind $sha1entry <<PasteSelection>> clearsha1
2113 bind $cflist <1> {sel_flist %W %x %y; break}
2114 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2115 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2116 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
2118 set maincursor [. cget -cursor]
2119 set textcursor [$ctext cget -cursor]
2120 set curtextcursor $textcursor
2122 set rowctxmenu .rowctxmenu
2123 menu $rowctxmenu -tearoff 0
2124 $rowctxmenu add command -label [mc "Diff this -> selected"] \
2125 -command {diffvssel 0}
2126 $rowctxmenu add command -label [mc "Diff selected -> this"] \
2127 -command {diffvssel 1}
2128 $rowctxmenu add command -label [mc "Make patch"] -command mkpatch
2129 $rowctxmenu add command -label [mc "Create tag"] -command mktag
2130 $rowctxmenu add command -label [mc "Write commit to file"] -command writecommit
2131 $rowctxmenu add command -label [mc "Create new branch"] -command mkbranch
2132 $rowctxmenu add command -label [mc "Cherry-pick this commit"] \
2134 $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \
2137 set fakerowmenu .fakerowmenu
2138 menu $fakerowmenu -tearoff 0
2139 $fakerowmenu add command -label [mc "Diff this -> selected"] \
2140 -command {diffvssel 0}
2141 $fakerowmenu add command -label [mc "Diff selected -> this"] \
2142 -command {diffvssel 1}
2143 $fakerowmenu add command -label [mc "Make patch"] -command mkpatch
2144 # $fakerowmenu add command -label [mc "Commit"] -command {mkcommit 0}
2145 # $fakerowmenu add command -label [mc "Commit all"] -command {mkcommit 1}
2146 # $fakerowmenu add command -label [mc "Revert local changes"] -command revertlocal
2148 set headctxmenu .headctxmenu
2149 menu $headctxmenu -tearoff 0
2150 $headctxmenu add command -label [mc "Check out this branch"] \
2152 $headctxmenu add command -label [mc "Remove this branch"] \
2156 set flist_menu .flistctxmenu
2157 menu $flist_menu -tearoff 0
2158 $flist_menu add command -label [mc "Highlight this too"] \
2159 -command {flist_hl 0}
2160 $flist_menu add command -label [mc "Highlight this only"] \
2161 -command {flist_hl 1}
2162 $flist_menu add command -label [mc "External diff"] \
2163 -command {external_diff}
2166 # Windows sends all mouse wheel events to the current focused window, not
2167 # the one where the mouse hovers, so bind those events here and redirect
2168 # to the correct window
2169 proc windows_mousewheel_redirector {W X Y D} {
2170 global canv canv2 canv3
2171 set w [winfo containing -displayof $W $X $Y]
2173 set u [expr {$D < 0 ? 5 : -5}]
2174 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2175 allcanvs yview scroll $u units
2178 $w yview scroll $u units
2184 # Update row number label when selectedline changes
2185 proc selectedline_change {n1 n2 op} {
2186 global selectedline rownumsel
2188 if {$op eq "unset"} {
2191 set rownumsel [expr {$selectedline + 1}]
2195 # mouse-2 makes all windows scan vertically, but only the one
2196 # the cursor is in scans horizontally
2197 proc canvscan {op w x y} {
2198 global canv canv2 canv3
2199 foreach c [list $canv $canv2 $canv3] {
2208 proc scrollcanv {cscroll f0 f1} {
2209 $cscroll set $f0 $f1
2214 # when we make a key binding for the toplevel, make sure
2215 # it doesn't get triggered when that key is pressed in the
2216 # find string entry widget.
2217 proc bindkey {ev script} {
2220 set escript [bind Entry $ev]
2221 if {$escript == {}} {
2222 set escript [bind Entry <Key>]
2224 foreach e $entries {
2225 bind $e $ev "$escript; break"
2229 # set the focus back to the toplevel for any click outside
2232 global ctext entries
2233 foreach e [concat $entries $ctext] {
2234 if {$w == $e} return
2239 # Adjust the progress bar for a change in requested extent or canvas size
2240 proc adjustprogress {} {
2241 global progresscanv progressitem progresscoords
2242 global fprogitem fprogcoord lastprogupdate progupdatepending
2243 global rprogitem rprogcoord
2245 set w [expr {[winfo width $progresscanv] - 4}]
2246 set x0 [expr {$w * [lindex $progresscoords 0]}]
2247 set x1 [expr {$w * [lindex $progresscoords 1]}]
2248 set h [winfo height $progresscanv]
2249 $progresscanv coords $progressitem $x0 0 $x1 $h
2250 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2251 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2252 set now [clock clicks -milliseconds]
2253 if {$now >= $lastprogupdate + 100} {
2254 set progupdatepending 0
2256 } elseif {!$progupdatepending} {
2257 set progupdatepending 1
2258 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2262 proc doprogupdate {} {
2263 global lastprogupdate progupdatepending
2265 if {$progupdatepending} {
2266 set progupdatepending 0
2267 set lastprogupdate [clock clicks -milliseconds]
2272 proc savestuff {w} {
2273 global canv canv2 canv3 mainfont textfont uifont tabstop
2274 global stuffsaved findmergefiles maxgraphpct
2275 global maxwidth showneartags showlocalchanges
2276 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2277 global cmitmode wrapcomment datetimeformat limitdiffs
2278 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2279 global autoselect extdifftool
2281 if {$stuffsaved} return
2282 if {![winfo viewable .]} return
2284 set f [open "~/.gitk-new" w]
2285 puts $f [list set mainfont $mainfont]
2286 puts $f [list set textfont $textfont]
2287 puts $f [list set uifont $uifont]
2288 puts $f [list set tabstop $tabstop]
2289 puts $f [list set findmergefiles $findmergefiles]
2290 puts $f [list set maxgraphpct $maxgraphpct]
2291 puts $f [list set maxwidth $maxwidth]
2292 puts $f [list set cmitmode $cmitmode]
2293 puts $f [list set wrapcomment $wrapcomment]
2294 puts $f [list set autoselect $autoselect]
2295 puts $f [list set showneartags $showneartags]
2296 puts $f [list set showlocalchanges $showlocalchanges]
2297 puts $f [list set datetimeformat $datetimeformat]
2298 puts $f [list set limitdiffs $limitdiffs]
2299 puts $f [list set bgcolor $bgcolor]
2300 puts $f [list set fgcolor $fgcolor]
2301 puts $f [list set colors $colors]
2302 puts $f [list set diffcolors $diffcolors]
2303 puts $f [list set diffcontext $diffcontext]
2304 puts $f [list set selectbgcolor $selectbgcolor]
2305 puts $f [list set extdifftool $extdifftool]
2307 puts $f "set geometry(main) [wm geometry .]"
2308 puts $f "set geometry(topwidth) [winfo width .tf]"
2309 puts $f "set geometry(topheight) [winfo height .tf]"
2310 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2311 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2312 puts $f "set geometry(botwidth) [winfo width .bleft]"
2313 puts $f "set geometry(botheight) [winfo height .bleft]"
2315 puts -nonewline $f "set permviews {"
2316 for {set v 0} {$v < $nextviewnum} {incr v} {
2317 if {$viewperm($v)} {
2318 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2323 catch {file delete "~/.gitk"}
2324 file rename -force "~/.gitk-new" "~/.gitk"
2329 proc resizeclistpanes {win w} {
2331 if {[info exists oldwidth($win)]} {
2332 set s0 [$win sash coord 0]
2333 set s1 [$win sash coord 1]
2335 set sash0 [expr {int($w/2 - 2)}]
2336 set sash1 [expr {int($w*5/6 - 2)}]
2338 set factor [expr {1.0 * $w / $oldwidth($win)}]
2339 set sash0 [expr {int($factor * [lindex $s0 0])}]
2340 set sash1 [expr {int($factor * [lindex $s1 0])}]
2344 if {$sash1 < $sash0 + 20} {
2345 set sash1 [expr {$sash0 + 20}]
2347 if {$sash1 > $w - 10} {
2348 set sash1 [expr {$w - 10}]
2349 if {$sash0 > $sash1 - 20} {
2350 set sash0 [expr {$sash1 - 20}]
2354 $win sash place 0 $sash0 [lindex $s0 1]
2355 $win sash place 1 $sash1 [lindex $s1 1]
2357 set oldwidth($win) $w
2360 proc resizecdetpanes {win w} {
2362 if {[info exists oldwidth($win)]} {
2363 set s0 [$win sash coord 0]
2365 set sash0 [expr {int($w*3/4 - 2)}]
2367 set factor [expr {1.0 * $w / $oldwidth($win)}]
2368 set sash0 [expr {int($factor * [lindex $s0 0])}]
2372 if {$sash0 > $w - 15} {
2373 set sash0 [expr {$w - 15}]
2376 $win sash place 0 $sash0 [lindex $s0 1]
2378 set oldwidth($win) $w
2381 proc allcanvs args {
2382 global canv canv2 canv3
2388 proc bindall {event action} {
2389 global canv canv2 canv3
2390 bind $canv $event $action
2391 bind $canv2 $event $action
2392 bind $canv3 $event $action
2398 if {[winfo exists $w]} {
2403 wm title $w [mc "About gitk"]
2404 message $w.m -text [mc "
2405 Gitk - a commit viewer for git
2407 Copyright © 2005-2008 Paul Mackerras
2409 Use and redistribute under the terms of the GNU General Public License"] \
2410 -justify center -aspect 400 -border 2 -bg white -relief groove
2411 pack $w.m -side top -fill x -padx 2 -pady 2
2412 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2413 pack $w.ok -side bottom
2414 bind $w <Visibility> "focus $w.ok"
2415 bind $w <Key-Escape> "destroy $w"
2416 bind $w <Key-Return> "destroy $w"
2421 if {[winfo exists $w]} {
2425 if {[tk windowingsystem] eq {aqua}} {
2431 wm title $w [mc "Gitk key bindings"]
2432 message $w.m -text "
2433 [mc "Gitk key bindings:"]
2435 [mc "<%s-Q> Quit" $M1T]
2436 [mc "<Home> Move to first commit"]
2437 [mc "<End> Move to last commit"]
2438 [mc "<Up>, p, i Move up one commit"]
2439 [mc "<Down>, n, k Move down one commit"]
2440 [mc "<Left>, z, j Go back in history list"]
2441 [mc "<Right>, x, l Go forward in history list"]
2442 [mc "<PageUp> Move up one page in commit list"]
2443 [mc "<PageDown> Move down one page in commit list"]
2444 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2445 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2446 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2447 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2448 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2449 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2450 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2451 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2452 [mc "<Delete>, b Scroll diff view up one page"]
2453 [mc "<Backspace> Scroll diff view up one page"]
2454 [mc "<Space> Scroll diff view down one page"]
2455 [mc "u Scroll diff view up 18 lines"]
2456 [mc "d Scroll diff view down 18 lines"]
2457 [mc "<%s-F> Find" $M1T]
2458 [mc "<%s-G> Move to next find hit" $M1T]
2459 [mc "<Return> Move to next find hit"]
2460 [mc "/ Move to next find hit, or redo find"]
2461 [mc "? Move to previous find hit"]
2462 [mc "f Scroll diff view to next file"]
2463 [mc "<%s-S> Search for next hit in diff view" $M1T]
2464 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2465 [mc "<%s-KP+> Increase font size" $M1T]
2466 [mc "<%s-plus> Increase font size" $M1T]
2467 [mc "<%s-KP-> Decrease font size" $M1T]
2468 [mc "<%s-minus> Decrease font size" $M1T]
2471 -justify left -bg white -border 2 -relief groove
2472 pack $w.m -side top -fill both -padx 2 -pady 2
2473 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2474 pack $w.ok -side bottom
2475 bind $w <Visibility> "focus $w.ok"
2476 bind $w <Key-Escape> "destroy $w"
2477 bind $w <Key-Return> "destroy $w"
2480 # Procedures for manipulating the file list window at the
2481 # bottom right of the overall window.
2483 proc treeview {w l openlevs} {
2484 global treecontents treediropen treeheight treeparent treeindex
2494 set treecontents() {}
2495 $w conf -state normal
2497 while {[string range $f 0 $prefixend] ne $prefix} {
2498 if {$lev <= $openlevs} {
2499 $w mark set e:$treeindex($prefix) "end -1c"
2500 $w mark gravity e:$treeindex($prefix) left
2502 set treeheight($prefix) $ht
2503 incr ht [lindex $htstack end]
2504 set htstack [lreplace $htstack end end]
2505 set prefixend [lindex $prefendstack end]
2506 set prefendstack [lreplace $prefendstack end end]
2507 set prefix [string range $prefix 0 $prefixend]
2510 set tail [string range $f [expr {$prefixend+1}] end]
2511 while {[set slash [string first "/" $tail]] >= 0} {
2514 lappend prefendstack $prefixend
2515 incr prefixend [expr {$slash + 1}]
2516 set d [string range $tail 0 $slash]
2517 lappend treecontents($prefix) $d
2518 set oldprefix $prefix
2520 set treecontents($prefix) {}
2521 set treeindex($prefix) [incr ix]
2522 set treeparent($prefix) $oldprefix
2523 set tail [string range $tail [expr {$slash+1}] end]
2524 if {$lev <= $openlevs} {
2526 set treediropen($prefix) [expr {$lev < $openlevs}]
2527 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2528 $w mark set d:$ix "end -1c"
2529 $w mark gravity d:$ix left
2531 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2533 $w image create end -align center -image $bm -padx 1 \
2535 $w insert end $d [highlight_tag $prefix]
2536 $w mark set s:$ix "end -1c"
2537 $w mark gravity s:$ix left
2542 if {$lev <= $openlevs} {
2545 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2547 $w insert end $tail [highlight_tag $f]
2549 lappend treecontents($prefix) $tail
2552 while {$htstack ne {}} {
2553 set treeheight($prefix) $ht
2554 incr ht [lindex $htstack end]
2555 set htstack [lreplace $htstack end end]
2556 set prefixend [lindex $prefendstack end]
2557 set prefendstack [lreplace $prefendstack end end]
2558 set prefix [string range $prefix 0 $prefixend]
2560 $w conf -state disabled
2563 proc linetoelt {l} {
2564 global treeheight treecontents
2569 foreach e $treecontents($prefix) {
2574 if {[string index $e end] eq "/"} {
2575 set n $treeheight($prefix$e)
2587 proc highlight_tree {y prefix} {
2588 global treeheight treecontents cflist
2590 foreach e $treecontents($prefix) {
2592 if {[highlight_tag $path] ne {}} {
2593 $cflist tag add bold $y.0 "$y.0 lineend"
2596 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2597 set y [highlight_tree $y $path]
2603 proc treeclosedir {w dir} {
2604 global treediropen treeheight treeparent treeindex
2606 set ix $treeindex($dir)
2607 $w conf -state normal
2608 $w delete s:$ix e:$ix
2609 set treediropen($dir) 0
2610 $w image configure a:$ix -image tri-rt
2611 $w conf -state disabled
2612 set n [expr {1 - $treeheight($dir)}]
2613 while {$dir ne {}} {
2614 incr treeheight($dir) $n
2615 set dir $treeparent($dir)
2619 proc treeopendir {w dir} {
2620 global treediropen treeheight treeparent treecontents treeindex
2622 set ix $treeindex($dir)
2623 $w conf -state normal
2624 $w image configure a:$ix -image tri-dn
2625 $w mark set e:$ix s:$ix
2626 $w mark gravity e:$ix right
2629 set n [llength $treecontents($dir)]
2630 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2633 incr treeheight($x) $n
2635 foreach e $treecontents($dir) {
2637 if {[string index $e end] eq "/"} {
2638 set iy $treeindex($de)
2639 $w mark set d:$iy e:$ix
2640 $w mark gravity d:$iy left
2641 $w insert e:$ix $str
2642 set treediropen($de) 0
2643 $w image create e:$ix -align center -image tri-rt -padx 1 \
2645 $w insert e:$ix $e [highlight_tag $de]
2646 $w mark set s:$iy e:$ix
2647 $w mark gravity s:$iy left
2648 set treeheight($de) 1
2650 $w insert e:$ix $str
2651 $w insert e:$ix $e [highlight_tag $de]
2654 $w mark gravity e:$ix left
2655 $w conf -state disabled
2656 set treediropen($dir) 1
2657 set top [lindex [split [$w index @0,0] .] 0]
2658 set ht [$w cget -height]
2659 set l [lindex [split [$w index s:$ix] .] 0]
2662 } elseif {$l + $n + 1 > $top + $ht} {
2663 set top [expr {$l + $n + 2 - $ht}]
2671 proc treeclick {w x y} {
2672 global treediropen cmitmode ctext cflist cflist_top
2674 if {$cmitmode ne "tree"} return
2675 if {![info exists cflist_top]} return
2676 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2677 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2678 $cflist tag add highlight $l.0 "$l.0 lineend"
2684 set e [linetoelt $l]
2685 if {[string index $e end] ne "/"} {
2687 } elseif {$treediropen($e)} {
2694 proc setfilelist {id} {
2695 global treefilelist cflist
2697 treeview $cflist $treefilelist($id) 0
2700 image create bitmap tri-rt -background black -foreground blue -data {
2701 #define tri-rt_width 13
2702 #define tri-rt_height 13
2703 static unsigned char tri-rt_bits[] = {
2704 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2705 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2708 #define tri-rt-mask_width 13
2709 #define tri-rt-mask_height 13
2710 static unsigned char tri-rt-mask_bits[] = {
2711 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2712 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2715 image create bitmap tri-dn -background black -foreground blue -data {
2716 #define tri-dn_width 13
2717 #define tri-dn_height 13
2718 static unsigned char tri-dn_bits[] = {
2719 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2720 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2723 #define tri-dn-mask_width 13
2724 #define tri-dn-mask_height 13
2725 static unsigned char tri-dn-mask_bits[] = {
2726 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2727 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2731 image create bitmap reficon-T -background black -foreground yellow -data {
2732 #define tagicon_width 13
2733 #define tagicon_height 9
2734 static unsigned char tagicon_bits[] = {
2735 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2736 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2738 #define tagicon-mask_width 13
2739 #define tagicon-mask_height 9
2740 static unsigned char tagicon-mask_bits[] = {
2741 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2742 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2745 #define headicon_width 13
2746 #define headicon_height 9
2747 static unsigned char headicon_bits[] = {
2748 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2749 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2752 #define headicon-mask_width 13
2753 #define headicon-mask_height 9
2754 static unsigned char headicon-mask_bits[] = {
2755 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2756 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2758 image create bitmap reficon-H -background black -foreground green \
2759 -data $rectdata -maskdata $rectmask
2760 image create bitmap reficon-o -background black -foreground "#ddddff" \
2761 -data $rectdata -maskdata $rectmask
2763 proc init_flist {first} {
2764 global cflist cflist_top difffilestart
2766 $cflist conf -state normal
2767 $cflist delete 0.0 end
2769 $cflist insert end $first
2771 $cflist tag add highlight 1.0 "1.0 lineend"
2773 catch {unset cflist_top}
2775 $cflist conf -state disabled
2776 set difffilestart {}
2779 proc highlight_tag {f} {
2780 global highlight_paths
2782 foreach p $highlight_paths {
2783 if {[string match $p $f]} {
2790 proc highlight_filelist {} {
2791 global cmitmode cflist
2793 $cflist conf -state normal
2794 if {$cmitmode ne "tree"} {
2795 set end [lindex [split [$cflist index end] .] 0]
2796 for {set l 2} {$l < $end} {incr l} {
2797 set line [$cflist get $l.0 "$l.0 lineend"]
2798 if {[highlight_tag $line] ne {}} {
2799 $cflist tag add bold $l.0 "$l.0 lineend"
2805 $cflist conf -state disabled
2808 proc unhighlight_filelist {} {
2811 $cflist conf -state normal
2812 $cflist tag remove bold 1.0 end
2813 $cflist conf -state disabled
2816 proc add_flist {fl} {
2819 $cflist conf -state normal
2821 $cflist insert end "\n"
2822 $cflist insert end $f [highlight_tag $f]
2824 $cflist conf -state disabled
2827 proc sel_flist {w x y} {
2828 global ctext difffilestart cflist cflist_top cmitmode
2830 if {$cmitmode eq "tree"} return
2831 if {![info exists cflist_top]} return
2832 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2833 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2834 $cflist tag add highlight $l.0 "$l.0 lineend"
2839 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2843 proc pop_flist_menu {w X Y x y} {
2844 global ctext cflist cmitmode flist_menu flist_menu_file
2845 global treediffs diffids
2848 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2850 if {$cmitmode eq "tree"} {
2851 set e [linetoelt $l]
2852 if {[string index $e end] eq "/"} return
2854 set e [lindex $treediffs($diffids) [expr {$l-2}]]
2856 set flist_menu_file $e
2857 set xdiffstate "normal"
2858 if {$cmitmode eq "tree"} {
2859 set xdiffstate "disabled"
2861 # Disable "External diff" item in tree mode
2862 $flist_menu entryconf 2 -state $xdiffstate
2863 tk_popup $flist_menu $X $Y
2866 proc flist_hl {only} {
2867 global flist_menu_file findstring gdttype
2869 set x [shellquote $flist_menu_file]
2870 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
2873 append findstring " " $x
2875 set gdttype [mc "touching paths:"]
2878 proc save_file_from_commit {filename output what} {
2881 if {[catch {exec git show $filename -- > $output} err]} {
2882 if {[string match "fatal: bad revision *" $err]} {
2885 error_popup "Error getting \"$filename\" from $what: $err"
2891 proc external_diff_get_one_file {diffid filename diffdir} {
2892 global nullid nullid2 nullfile
2895 if {$diffid == $nullid} {
2896 set difffile [file join [file dirname $gitdir] $filename]
2897 if {[file exists $difffile]} {
2902 if {$diffid == $nullid2} {
2903 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
2904 return [save_file_from_commit :$filename $difffile index]
2906 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
2907 return [save_file_from_commit $diffid:$filename $difffile \
2911 proc external_diff {} {
2912 global gitktmpdir nullid nullid2
2913 global flist_menu_file
2916 global gitdir extdifftool
2918 if {[llength $diffids] == 1} {
2919 # no reference commit given
2920 set diffidto [lindex $diffids 0]
2921 if {$diffidto eq $nullid} {
2922 # diffing working copy with index
2923 set diffidfrom $nullid2
2924 } elseif {$diffidto eq $nullid2} {
2925 # diffing index with HEAD
2926 set diffidfrom "HEAD"
2928 # use first parent commit
2929 global parentlist selectedline
2930 set diffidfrom [lindex $parentlist $selectedline 0]
2933 set diffidfrom [lindex $diffids 0]
2934 set diffidto [lindex $diffids 1]
2937 # make sure that several diffs wont collide
2938 if {![info exists gitktmpdir]} {
2939 set gitktmpdir [file join [file dirname $gitdir] \
2940 [format ".gitk-tmp.%s" [pid]]]
2941 if {[catch {file mkdir $gitktmpdir} err]} {
2942 error_popup "Error creating temporary directory $gitktmpdir: $err"
2949 set diffdir [file join $gitktmpdir $diffnum]
2950 if {[catch {file mkdir $diffdir} err]} {
2951 error_popup "Error creating temporary directory $diffdir: $err"
2955 # gather files to diff
2956 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
2957 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
2959 if {$difffromfile ne {} && $difftofile ne {}} {
2960 set cmd [concat | [shellsplit $extdifftool] \
2961 [list $difffromfile $difftofile]]
2962 if {[catch {set fl [open $cmd r]} err]} {
2963 file delete -force $diffdir
2964 error_popup [mc "$extdifftool: command failed: $err"]
2966 fconfigure $fl -blocking 0
2967 filerun $fl [list delete_at_eof $fl $diffdir]
2972 # delete $dir when we see eof on $f (presumably because the child has exited)
2973 proc delete_at_eof {f dir} {
2974 while {[gets $f line] >= 0} {}
2976 if {[catch {close $f} err]} {
2977 error_popup "External diff viewer failed: $err"
2979 file delete -force $dir
2985 # Functions for adding and removing shell-type quoting
2987 proc shellquote {str} {
2988 if {![string match "*\['\"\\ \t]*" $str]} {
2991 if {![string match "*\['\"\\]*" $str]} {
2994 if {![string match "*'*" $str]} {
2997 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3000 proc shellarglist {l} {
3006 append str [shellquote $a]
3011 proc shelldequote {str} {
3016 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3017 append ret [string range $str $used end]
3018 set used [string length $str]
3021 set first [lindex $first 0]
3022 set ch [string index $str $first]
3023 if {$first > $used} {
3024 append ret [string range $str $used [expr {$first - 1}]]
3027 if {$ch eq " " || $ch eq "\t"} break
3030 set first [string first "'" $str $used]
3032 error "unmatched single-quote"
3034 append ret [string range $str $used [expr {$first - 1}]]
3039 if {$used >= [string length $str]} {
3040 error "trailing backslash"
3042 append ret [string index $str $used]
3047 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3048 error "unmatched double-quote"
3050 set first [lindex $first 0]
3051 set ch [string index $str $first]
3052 if {$first > $used} {
3053 append ret [string range $str $used [expr {$first - 1}]]
3056 if {$ch eq "\""} break
3058 append ret [string index $str $used]
3062 return [list $used $ret]
3065 proc shellsplit {str} {
3068 set str [string trimleft $str]
3069 if {$str eq {}} break
3070 set dq [shelldequote $str]
3071 set n [lindex $dq 0]
3072 set word [lindex $dq 1]
3073 set str [string range $str $n end]
3079 # Code to implement multiple views
3081 proc newview {ishighlight} {
3082 global nextviewnum newviewname newviewperm newishighlight
3083 global newviewargs revtreeargs viewargscmd newviewargscmd curview
3085 set newishighlight $ishighlight
3087 if {[winfo exists $top]} {
3091 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3092 set newviewperm($nextviewnum) 0
3093 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
3094 set newviewargscmd($nextviewnum) $viewargscmd($curview)
3095 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3100 global viewname viewperm newviewname newviewperm
3101 global viewargs newviewargs viewargscmd newviewargscmd
3103 set top .gitkvedit-$curview
3104 if {[winfo exists $top]} {
3108 set newviewname($curview) $viewname($curview)
3109 set newviewperm($curview) $viewperm($curview)
3110 set newviewargs($curview) [shellarglist $viewargs($curview)]
3111 set newviewargscmd($curview) $viewargscmd($curview)
3112 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3115 proc vieweditor {top n title} {
3116 global newviewname newviewperm viewfiles bgcolor
3119 wm title $top $title
3120 label $top.nl -text [mc "Name"]
3121 entry $top.name -width 20 -textvariable newviewname($n)
3122 grid $top.nl $top.name -sticky w -pady 5
3123 checkbutton $top.perm -text [mc "Remember this view"] \
3124 -variable newviewperm($n)
3125 grid $top.perm - -pady 5 -sticky w
3126 message $top.al -aspect 1000 \
3127 -text [mc "Commits to include (arguments to git log):"]
3128 grid $top.al - -sticky w -pady 5
3129 entry $top.args -width 50 -textvariable newviewargs($n) \
3130 -background $bgcolor
3131 grid $top.args - -sticky ew -padx 5
3133 message $top.ac -aspect 1000 \
3134 -text [mc "Command to generate more commits to include:"]
3135 grid $top.ac - -sticky w -pady 5
3136 entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
3138 grid $top.argscmd - -sticky ew -padx 5
3140 message $top.l -aspect 1000 \
3141 -text [mc "Enter files and directories to include, one per line:"]
3142 grid $top.l - -sticky w
3143 text $top.t -width 40 -height 10 -background $bgcolor -font uifont
3144 if {[info exists viewfiles($n)]} {
3145 foreach f $viewfiles($n) {
3146 $top.t insert end $f
3147 $top.t insert end "\n"
3149 $top.t delete {end - 1c} end
3150 $top.t mark set insert 0.0
3152 grid $top.t - -sticky ew -padx 5
3154 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3155 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3156 grid $top.buts.ok $top.buts.can
3157 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3158 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3159 grid $top.buts - -pady 10 -sticky ew
3163 proc doviewmenu {m first cmd op argv} {
3164 set nmenu [$m index end]
3165 for {set i $first} {$i <= $nmenu} {incr i} {
3166 if {[$m entrycget $i -command] eq $cmd} {
3167 eval $m $op $i $argv
3173 proc allviewmenus {n op args} {
3176 doviewmenu .bar.view 5 [list showview $n] $op $args
3177 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3180 proc newviewok {top n} {
3181 global nextviewnum newviewperm newviewname newishighlight
3182 global viewname viewfiles viewperm selectedview curview
3183 global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
3186 set newargs [shellsplit $newviewargs($n)]
3188 error_popup "[mc "Error in commit selection arguments:"] $err"
3194 foreach f [split [$top.t get 0.0 end] "\n"] {
3195 set ft [string trim $f]
3200 if {![info exists viewfiles($n)]} {
3201 # creating a new view
3203 set viewname($n) $newviewname($n)
3204 set viewperm($n) $newviewperm($n)
3205 set viewfiles($n) $files
3206 set viewargs($n) $newargs
3207 set viewargscmd($n) $newviewargscmd($n)
3209 if {!$newishighlight} {
3212 run addvhighlight $n
3215 # editing an existing view
3216 set viewperm($n) $newviewperm($n)
3217 if {$newviewname($n) ne $viewname($n)} {
3218 set viewname($n) $newviewname($n)
3219 doviewmenu .bar.view 5 [list showview $n] \
3220 entryconf [list -label $viewname($n)]
3221 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3222 # entryconf [list -label $viewname($n) -value $viewname($n)]
3224 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3225 $newviewargscmd($n) ne $viewargscmd($n)} {
3226 set viewfiles($n) $files
3227 set viewargs($n) $newargs
3228 set viewargscmd($n) $newviewargscmd($n)
3229 if {$curview == $n} {
3234 catch {destroy $top}
3238 global curview viewperm hlview selectedhlview
3240 if {$curview == 0} return
3241 if {[info exists hlview] && $hlview == $curview} {
3242 set selectedhlview [mc "None"]
3245 allviewmenus $curview delete
3246 set viewperm($curview) 0
3250 proc addviewmenu {n} {
3251 global viewname viewhlmenu
3253 .bar.view add radiobutton -label $viewname($n) \
3254 -command [list showview $n] -variable selectedview -value $n
3255 #$viewhlmenu add radiobutton -label $viewname($n) \
3256 # -command [list addvhighlight $n] -variable selectedhlview
3260 global curview cached_commitrow ordertok
3261 global displayorder parentlist rowidlist rowisopt rowfinal
3262 global colormap rowtextx nextcolor canvxmax
3263 global numcommits viewcomplete
3264 global selectedline currentid canv canvy0
3266 global pending_select mainheadid
3269 global hlview selectedhlview commitinterest
3271 if {$n == $curview} return
3273 set ymax [lindex [$canv cget -scrollregion] 3]
3274 set span [$canv yview]
3275 set ytop [expr {[lindex $span 0] * $ymax}]
3276 set ybot [expr {[lindex $span 1] * $ymax}]
3277 set yscreen [expr {($ybot - $ytop) / 2}]
3278 if {[info exists selectedline]} {
3279 set selid $currentid
3280 set y [yc $selectedline]
3281 if {$ytop < $y && $y < $ybot} {
3282 set yscreen [expr {$y - $ytop}]
3284 } elseif {[info exists pending_select]} {
3285 set selid $pending_select
3286 unset pending_select
3290 catch {unset treediffs}
3292 if {[info exists hlview] && $hlview == $n} {
3294 set selectedhlview [mc "None"]
3296 catch {unset commitinterest}
3297 catch {unset cached_commitrow}
3298 catch {unset ordertok}
3302 .bar.view entryconf [mc "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3303 .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3306 if {![info exists viewcomplete($n)]} {
3308 set pending_select $selid
3319 set numcommits $commitidx($n)
3321 catch {unset colormap}
3322 catch {unset rowtextx}
3324 set canvxmax [$canv cget -width]
3330 if {$selid ne {} && [commitinview $selid $n]} {
3331 set row [rowofcommit $selid]
3332 # try to get the selected row in the same position on the screen
3333 set ymax [lindex [$canv cget -scrollregion] 3]
3334 set ytop [expr {[yc $row] - $yscreen}]
3338 set yf [expr {$ytop * 1.0 / $ymax}]
3340 allcanvs yview moveto $yf
3344 } elseif {$mainheadid ne {} && [commitinview $mainheadid $curview]} {
3345 selectline [rowofcommit $mainheadid] 1
3346 } elseif {!$viewcomplete($n)} {
3348 set pending_select $selid
3350 set pending_select $mainheadid
3353 set row [first_real_row]
3354 if {$row < $numcommits} {
3358 if {!$viewcomplete($n)} {
3359 if {$numcommits == 0} {
3360 show_status [mc "Reading commits..."]
3362 } elseif {$numcommits == 0} {
3363 show_status [mc "No commits selected"]
3367 # Stuff relating to the highlighting facility
3369 proc ishighlighted {id} {
3370 global vhighlights fhighlights nhighlights rhighlights
3372 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3373 return $nhighlights($id)
3375 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3376 return $vhighlights($id)
3378 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3379 return $fhighlights($id)
3381 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3382 return $rhighlights($id)
3387 proc bolden {row font} {
3388 global canv linehtag selectedline boldrows
3390 lappend boldrows $row
3391 $canv itemconf $linehtag($row) -font $font
3392 if {[info exists selectedline] && $row == $selectedline} {
3394 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3395 -outline {{}} -tags secsel \
3396 -fill [$canv cget -selectbackground]]
3401 proc bolden_name {row font} {
3402 global canv2 linentag selectedline boldnamerows
3404 lappend boldnamerows $row
3405 $canv2 itemconf $linentag($row) -font $font
3406 if {[info exists selectedline] && $row == $selectedline} {
3407 $canv2 delete secsel
3408 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3409 -outline {{}} -tags secsel \
3410 -fill [$canv2 cget -selectbackground]]
3419 foreach row $boldrows {
3420 if {![ishighlighted [commitonrow $row]]} {
3421 bolden $row mainfont
3423 lappend stillbold $row
3426 set boldrows $stillbold
3429 proc addvhighlight {n} {
3430 global hlview viewcomplete curview vhl_done commitidx
3432 if {[info exists hlview]} {
3436 if {$n != $curview && ![info exists viewcomplete($n)]} {
3439 set vhl_done $commitidx($hlview)
3440 if {$vhl_done > 0} {
3445 proc delvhighlight {} {
3446 global hlview vhighlights
3448 if {![info exists hlview]} return
3450 catch {unset vhighlights}
3454 proc vhighlightmore {} {
3455 global hlview vhl_done commitidx vhighlights curview
3457 set max $commitidx($hlview)
3458 set vr [visiblerows]
3459 set r0 [lindex $vr 0]
3460 set r1 [lindex $vr 1]
3461 for {set i $vhl_done} {$i < $max} {incr i} {
3462 set id [commitonrow $i $hlview]
3463 if {[commitinview $id $curview]} {
3464 set row [rowofcommit $id]
3465 if {$r0 <= $row && $row <= $r1} {
3466 if {![highlighted $row]} {
3467 bolden $row mainfontbold
3469 set vhighlights($id) 1
3477 proc askvhighlight {row id} {
3478 global hlview vhighlights iddrawn
3480 if {[commitinview $id $hlview]} {
3481 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
3482 bolden $row mainfontbold
3484 set vhighlights($id) 1
3486 set vhighlights($id) 0
3490 proc hfiles_change {} {
3491 global highlight_files filehighlight fhighlights fh_serial
3492 global highlight_paths gdttype
3494 if {[info exists filehighlight]} {
3495 # delete previous highlights
3496 catch {close $filehighlight}
3498 catch {unset fhighlights}
3500 unhighlight_filelist
3502 set highlight_paths {}
3503 after cancel do_file_hl $fh_serial
3505 if {$highlight_files ne {}} {
3506 after 300 do_file_hl $fh_serial
3510 proc gdttype_change {name ix op} {
3511 global gdttype highlight_files findstring findpattern
3514 if {$findstring ne {}} {
3515 if {$gdttype eq [mc "containing:"]} {
3516 if {$highlight_files ne {}} {
3517 set highlight_files {}
3522 if {$findpattern ne {}} {
3526 set highlight_files $findstring
3531 # enable/disable findtype/findloc menus too
3534 proc find_change {name ix op} {
3535 global gdttype findstring highlight_files
3538 if {$gdttype eq [mc "containing:"]} {
3541 if {$highlight_files ne $findstring} {
3542 set highlight_files $findstring
3549 proc findcom_change args {
3550 global nhighlights boldnamerows
3551 global findpattern findtype findstring gdttype
3554 # delete previous highlights, if any
3555 foreach row $boldnamerows {
3556 bolden_name $row mainfont
3559 catch {unset nhighlights}
3562 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
3564 } elseif {$findtype eq [mc "Regexp"]} {
3565 set findpattern $findstring
3567 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3569 set findpattern "*$e*"
3573 proc makepatterns {l} {
3576 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3577 if {[string index $ee end] eq "/"} {
3587 proc do_file_hl {serial} {
3588 global highlight_files filehighlight highlight_paths gdttype fhl_list
3590 if {$gdttype eq [mc "touching paths:"]} {
3591 if {[catch {set paths [shellsplit $highlight_files]}]} return
3592 set highlight_paths [makepatterns $paths]
3594 set gdtargs [concat -- $paths]
3595 } elseif {$gdttype eq [mc "adding/removing string:"]} {
3596 set gdtargs [list "-S$highlight_files"]
3598 # must be "containing:", i.e. we're searching commit info
3601 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3602 set filehighlight [open $cmd r+]
3603 fconfigure $filehighlight -blocking 0
3604 filerun $filehighlight readfhighlight
3610 proc flushhighlights {} {
3611 global filehighlight fhl_list
3613 if {[info exists filehighlight]} {
3615 puts $filehighlight ""
3616 flush $filehighlight
3620 proc askfilehighlight {row id} {
3621 global filehighlight fhighlights fhl_list
3623 lappend fhl_list $id
3624 set fhighlights($id) -1
3625 puts $filehighlight $id
3628 proc readfhighlight {} {
3629 global filehighlight fhighlights curview iddrawn
3630 global fhl_list find_dirn
3632 if {![info exists filehighlight]} {
3636 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
3637 set line [string trim $line]
3638 set i [lsearch -exact $fhl_list $line]
3639 if {$i < 0} continue
3640 for {set j 0} {$j < $i} {incr j} {
3641 set id [lindex $fhl_list $j]
3642 set fhighlights($id) 0
3644 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
3645 if {$line eq {}} continue
3646 if {![commitinview $line $curview]} continue
3647 set row [rowofcommit $line]
3648 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
3649 bolden $row mainfontbold
3651 set fhighlights($line) 1
3653 if {[eof $filehighlight]} {
3655 puts "oops, git diff-tree died"
3656 catch {close $filehighlight}
3660 if {[info exists find_dirn]} {
3666 proc doesmatch {f} {
3667 global findtype findpattern
3669 if {$findtype eq [mc "Regexp"]} {
3670 return [regexp $findpattern $f]
3671 } elseif {$findtype eq [mc "IgnCase"]} {
3672 return [string match -nocase $findpattern $f]
3674 return [string match $findpattern $f]
3678 proc askfindhighlight {row id} {
3679 global nhighlights commitinfo iddrawn
3681 global markingmatches
3683 if {![info exists commitinfo($id)]} {
3686 set info $commitinfo($id)
3688 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
3689 foreach f $info ty $fldtypes {
3690 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
3692 if {$ty eq [mc "Author"]} {
3699 if {$isbold && [info exists iddrawn($id)]} {
3700 if {![ishighlighted $id]} {
3701 bolden $row mainfontbold
3703 bolden_name $row mainfontbold
3706 if {$markingmatches} {
3707 markrowmatches $row $id
3710 set nhighlights($id) $isbold
3713 proc markrowmatches {row id} {
3714 global canv canv2 linehtag linentag commitinfo findloc
3716 set headline [lindex $commitinfo($id) 0]
3717 set author [lindex $commitinfo($id) 1]
3718 $canv delete match$row
3719 $canv2 delete match$row
3720 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
3721 set m [findmatches $headline]
3723 markmatches $canv $row $headline $linehtag($row) $m \
3724 [$canv itemcget $linehtag($row) -font] $row
3727 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
3728 set m [findmatches $author]
3730 markmatches $canv2 $row $author $linentag($row) $m \
3731 [$canv2 itemcget $linentag($row) -font] $row
3736 proc vrel_change {name ix op} {
3737 global highlight_related
3740 if {$highlight_related ne [mc "None"]} {
3745 # prepare for testing whether commits are descendents or ancestors of a
3746 proc rhighlight_sel {a} {
3747 global descendent desc_todo ancestor anc_todo
3748 global highlight_related
3750 catch {unset descendent}
3751 set desc_todo [list $a]
3752 catch {unset ancestor}
3753 set anc_todo [list $a]
3754 if {$highlight_related ne [mc "None"]} {
3760 proc rhighlight_none {} {
3763 catch {unset rhighlights}
3767 proc is_descendent {a} {
3768 global curview children descendent desc_todo
3771 set la [rowofcommit $a]
3775 for {set i 0} {$i < [llength $todo]} {incr i} {
3776 set do [lindex $todo $i]
3777 if {[rowofcommit $do] < $la} {
3778 lappend leftover $do
3781 foreach nk $children($v,$do) {
3782 if {![info exists descendent($nk)]} {
3783 set descendent($nk) 1
3791 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3795 set descendent($a) 0
3796 set desc_todo $leftover
3799 proc is_ancestor {a} {
3800 global curview parents ancestor anc_todo
3803 set la [rowofcommit $a]
3807 for {set i 0} {$i < [llength $todo]} {incr i} {
3808 set do [lindex $todo $i]
3809 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
3810 lappend leftover $do
3813 foreach np $parents($v,$do) {
3814 if {![info exists ancestor($np)]} {
3823 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3828 set anc_todo $leftover
3831 proc askrelhighlight {row id} {
3832 global descendent highlight_related iddrawn rhighlights
3833 global selectedline ancestor
3835 if {![info exists selectedline]} return
3837 if {$highlight_related eq [mc "Descendant"] ||
3838 $highlight_related eq [mc "Not descendant"]} {
3839 if {![info exists descendent($id)]} {
3842 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
3845 } elseif {$highlight_related eq [mc "Ancestor"] ||
3846 $highlight_related eq [mc "Not ancestor"]} {
3847 if {![info exists ancestor($id)]} {
3850 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
3854 if {[info exists iddrawn($id)]} {
3855 if {$isbold && ![ishighlighted $id]} {
3856 bolden $row mainfontbold
3859 set rhighlights($id) $isbold
3862 # Graph layout functions
3864 proc shortids {ids} {
3867 if {[llength $id] > 1} {
3868 lappend res [shortids $id]
3869 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
3870 lappend res [string range $id 0 7]
3881 for {set mask 1} {$mask <= $n} {incr mask $mask} {
3882 if {($n & $mask) != 0} {
3883 set ret [concat $ret $o]
3885 set o [concat $o $o]
3890 proc ordertoken {id} {
3891 global ordertok curview varcid varcstart varctok curview parents children
3892 global nullid nullid2
3894 if {[info exists ordertok($id)]} {
3895 return $ordertok($id)
3900 if {[info exists varcid($curview,$id)]} {
3901 set a $varcid($curview,$id)
3902 set p [lindex $varcstart($curview) $a]
3904 set p [lindex $children($curview,$id) 0]
3906 if {[info exists ordertok($p)]} {
3907 set tok $ordertok($p)
3910 set id [first_real_child $curview,$p]
3913 set tok [lindex $varctok($curview) $varcid($curview,$p)]
3916 if {[llength $parents($curview,$id)] == 1} {
3917 lappend todo [list $p {}]
3919 set j [lsearch -exact $parents($curview,$id) $p]
3921 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
3923 lappend todo [list $p [strrep $j]]
3926 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
3927 set p [lindex $todo $i 0]
3928 append tok [lindex $todo $i 1]
3929 set ordertok($p) $tok
3931 set ordertok($origid) $tok
3935 # Work out where id should go in idlist so that order-token
3936 # values increase from left to right
3937 proc idcol {idlist id {i 0}} {
3938 set t [ordertoken $id]
3942 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
3943 if {$i > [llength $idlist]} {
3944 set i [llength $idlist]
3946 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
3949 if {$t > [ordertoken [lindex $idlist $i]]} {
3950 while {[incr i] < [llength $idlist] &&
3951 $t >= [ordertoken [lindex $idlist $i]]} {}
3957 proc initlayout {} {
3958 global rowidlist rowisopt rowfinal displayorder parentlist
3959 global numcommits canvxmax canv
3961 global colormap rowtextx
3970 set canvxmax [$canv cget -width]
3971 catch {unset colormap}
3972 catch {unset rowtextx}
3976 proc setcanvscroll {} {
3977 global canv canv2 canv3 numcommits linespc canvxmax canvy0
3978 global lastscrollset lastscrollrows
3980 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
3981 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
3982 $canv2 conf -scrollregion [list 0 0 0 $ymax]
3983 $canv3 conf -scrollregion [list 0 0 0 $ymax]
3984 set lastscrollset [clock clicks -milliseconds]
3985 set lastscrollrows $numcommits
3988 proc visiblerows {} {
3989 global canv numcommits linespc
3991 set ymax [lindex [$canv cget -scrollregion] 3]
3992 if {$ymax eq {} || $ymax == 0} return
3994 set y0 [expr {int([lindex $f 0] * $ymax)}]
3995 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
3999 set y1 [expr {int([lindex $f 1] * $ymax)}]
4000 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4001 if {$r1 >= $numcommits} {
4002 set r1 [expr {$numcommits - 1}]
4004 return [list $r0 $r1]
4007 proc layoutmore {} {
4008 global commitidx viewcomplete curview
4009 global numcommits pending_select selectedline curview
4010 global lastscrollset lastscrollrows commitinterest
4012 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4013 [clock clicks -milliseconds] - $lastscrollset > 500} {
4016 if {[info exists pending_select] &&
4017 [commitinview $pending_select $curview]} {
4018 selectline [rowofcommit $pending_select] 1
4023 proc doshowlocalchanges {} {
4024 global curview mainheadid
4026 if {[commitinview $mainheadid $curview]} {
4029 lappend commitinterest($mainheadid) {dodiffindex}
4033 proc dohidelocalchanges {} {
4034 global nullid nullid2 lserial curview
4036 if {[commitinview $nullid $curview]} {
4037 removefakerow $nullid
4039 if {[commitinview $nullid2 $curview]} {
4040 removefakerow $nullid2
4045 # spawn off a process to do git diff-index --cached HEAD
4046 proc dodiffindex {} {
4047 global lserial showlocalchanges
4050 if {!$showlocalchanges || !$isworktree} return
4052 set fd [open "|git diff-index --cached HEAD" r]
4053 fconfigure $fd -blocking 0
4054 filerun $fd [list readdiffindex $fd $lserial]
4057 proc readdiffindex {fd serial} {
4058 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4061 if {[gets $fd line] < 0} {
4067 # we only need to see one line and we don't really care what it says...
4070 if {$serial != $lserial} {
4074 # now see if there are any local changes not checked in to the index
4075 set fd [open "|git diff-files" r]
4076 fconfigure $fd -blocking 0
4077 filerun $fd [list readdifffiles $fd $serial]
4079 if {$isdiff && ![commitinview $nullid2 $curview]} {
4080 # add the line for the changes in the index to the graph
4081 set hl [mc "Local changes checked in to index but not committed"]
4082 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4083 set commitdata($nullid2) "\n $hl\n"
4084 if {[commitinview $nullid $curview]} {
4085 removefakerow $nullid
4087 insertfakerow $nullid2 $mainheadid
4088 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4089 removefakerow $nullid2
4094 proc readdifffiles {fd serial} {
4095 global mainheadid nullid nullid2 curview
4096 global commitinfo commitdata lserial
4099 if {[gets $fd line] < 0} {
4105 # we only need to see one line and we don't really care what it says...
4108 if {$serial != $lserial} {
4112 if {$isdiff && ![commitinview $nullid $curview]} {
4113 # add the line for the local diff to the graph
4114 set hl [mc "Local uncommitted changes, not checked in to index"]
4115 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4116 set commitdata($nullid) "\n $hl\n"
4117 if {[commitinview $nullid2 $curview]} {
4122 insertfakerow $nullid $p
4123 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4124 removefakerow $nullid
4129 proc nextuse {id row} {
4130 global curview children
4132 if {[info exists children($curview,$id)]} {
4133 foreach kid $children($curview,$id) {
4134 if {![commitinview $kid $curview]} {
4137 if {[rowofcommit $kid] > $row} {
4138 return [rowofcommit $kid]
4142 if {[commitinview $id $curview]} {
4143 return [rowofcommit $id]
4148 proc prevuse {id row} {
4149 global curview children
4152 if {[info exists children($curview,$id)]} {
4153 foreach kid $children($curview,$id) {
4154 if {![commitinview $kid $curview]} break
4155 if {[rowofcommit $kid] < $row} {
4156 set ret [rowofcommit $kid]
4163 proc make_idlist {row} {
4164 global displayorder parentlist uparrowlen downarrowlen mingaplen
4165 global commitidx curview children
4167 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4171 set ra [expr {$row - $downarrowlen}]
4175 set rb [expr {$row + $uparrowlen}]
4176 if {$rb > $commitidx($curview)} {
4177 set rb $commitidx($curview)
4179 make_disporder $r [expr {$rb + 1}]
4181 for {} {$r < $ra} {incr r} {
4182 set nextid [lindex $displayorder [expr {$r + 1}]]
4183 foreach p [lindex $parentlist $r] {
4184 if {$p eq $nextid} continue
4185 set rn [nextuse $p $r]
4187 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4188 lappend ids [list [ordertoken $p] $p]
4192 for {} {$r < $row} {incr r} {
4193 set nextid [lindex $displayorder [expr {$r + 1}]]
4194 foreach p [lindex $parentlist $r] {
4195 if {$p eq $nextid} continue
4196 set rn [nextuse $p $r]
4197 if {$rn < 0 || $rn >= $row} {
4198 lappend ids [list [ordertoken $p] $p]
4202 set id [lindex $displayorder $row]
4203 lappend ids [list [ordertoken $id] $id]
4205 foreach p [lindex $parentlist $r] {
4206 set firstkid [lindex $children($curview,$p) 0]
4207 if {[rowofcommit $firstkid] < $row} {
4208 lappend ids [list [ordertoken $p] $p]
4212 set id [lindex $displayorder $r]
4214 set firstkid [lindex $children($curview,$id) 0]
4215 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4216 lappend ids [list [ordertoken $id] $id]
4221 foreach idx [lsort -unique $ids] {
4222 lappend idlist [lindex $idx 1]
4227 proc rowsequal {a b} {
4228 while {[set i [lsearch -exact $a {}]] >= 0} {
4229 set a [lreplace $a $i $i]
4231 while {[set i [lsearch -exact $b {}]] >= 0} {
4232 set b [lreplace $b $i $i]
4234 return [expr {$a eq $b}]
4237 proc makeupline {id row rend col} {
4238 global rowidlist uparrowlen downarrowlen mingaplen
4240 for {set r $rend} {1} {set r $rstart} {
4241 set rstart [prevuse $id $r]
4242 if {$rstart < 0} return
4243 if {$rstart < $row} break
4245 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4246 set rstart [expr {$rend - $uparrowlen - 1}]
4248 for {set r $rstart} {[incr r] <= $row} {} {
4249 set idlist [lindex $rowidlist $r]
4250 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4251 set col [idcol $idlist $id $col]
4252 lset rowidlist $r [linsert $idlist $col $id]
4258 proc layoutrows {row endrow} {
4259 global rowidlist rowisopt rowfinal displayorder
4260 global uparrowlen downarrowlen maxwidth mingaplen
4261 global children parentlist
4262 global commitidx viewcomplete curview
4264 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4267 set rm1 [expr {$row - 1}]
4268 foreach id [lindex $rowidlist $rm1] {
4273 set final [lindex $rowfinal $rm1]
4275 for {} {$row < $endrow} {incr row} {
4276 set rm1 [expr {$row - 1}]
4277 if {$rm1 < 0 || $idlist eq {}} {
4278 set idlist [make_idlist $row]
4281 set id [lindex $displayorder $rm1]
4282 set col [lsearch -exact $idlist $id]
4283 set idlist [lreplace $idlist $col $col]
4284 foreach p [lindex $parentlist $rm1] {
4285 if {[lsearch -exact $idlist $p] < 0} {
4286 set col [idcol $idlist $p $col]
4287 set idlist [linsert $idlist $col $p]
4288 # if not the first child, we have to insert a line going up
4289 if {$id ne [lindex $children($curview,$p) 0]} {
4290 makeupline $p $rm1 $row $col
4294 set id [lindex $displayorder $row]
4295 if {$row > $downarrowlen} {
4296 set termrow [expr {$row - $downarrowlen - 1}]
4297 foreach p [lindex $parentlist $termrow] {
4298 set i [lsearch -exact $idlist $p]
4299 if {$i < 0} continue
4300 set nr [nextuse $p $termrow]
4301 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4302 set idlist [lreplace $idlist $i $i]
4306 set col [lsearch -exact $idlist $id]
4308 set col [idcol $idlist $id]
4309 set idlist [linsert $idlist $col $id]
4310 if {$children($curview,$id) ne {}} {
4311 makeupline $id $rm1 $row $col
4314 set r [expr {$row + $uparrowlen - 1}]
4315 if {$r < $commitidx($curview)} {
4317 foreach p [lindex $parentlist $r] {
4318 if {[lsearch -exact $idlist $p] >= 0} continue
4319 set fk [lindex $children($curview,$p) 0]
4320 if {[rowofcommit $fk] < $row} {
4321 set x [idcol $idlist $p $x]
4322 set idlist [linsert $idlist $x $p]
4325 if {[incr r] < $commitidx($curview)} {
4326 set p [lindex $displayorder $r]
4327 if {[lsearch -exact $idlist $p] < 0} {
4328 set fk [lindex $children($curview,$p) 0]
4329 if {$fk ne {} && [rowofcommit $fk] < $row} {
4330 set x [idcol $idlist $p $x]
4331 set idlist [linsert $idlist $x $p]
4337 if {$final && !$viewcomplete($curview) &&
4338 $row + $uparrowlen + $mingaplen + $downarrowlen
4339 >= $commitidx($curview)} {
4342 set l [llength $rowidlist]
4344 lappend rowidlist $idlist
4346 lappend rowfinal $final
4347 } elseif {$row < $l} {
4348 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4349 lset rowidlist $row $idlist
4352 lset rowfinal $row $final
4354 set pad [ntimes [expr {$row - $l}] {}]
4355 set rowidlist [concat $rowidlist $pad]
4356 lappend rowidlist $idlist
4357 set rowfinal [concat $rowfinal $pad]
4358 lappend rowfinal $final
4359 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4365 proc changedrow {row} {
4366 global displayorder iddrawn rowisopt need_redisplay
4368 set l [llength $rowisopt]
4370 lset rowisopt $row 0
4371 if {$row + 1 < $l} {
4372 lset rowisopt [expr {$row + 1}] 0
4373 if {$row + 2 < $l} {
4374 lset rowisopt [expr {$row + 2}] 0
4378 set id [lindex $displayorder $row]
4379 if {[info exists iddrawn($id)]} {
4380 set need_redisplay 1
4384 proc insert_pad {row col npad} {
4387 set pad [ntimes $npad {}]
4388 set idlist [lindex $rowidlist $row]
4389 set bef [lrange $idlist 0 [expr {$col - 1}]]
4390 set aft [lrange $idlist $col end]
4391 set i [lsearch -exact $aft {}]
4393 set aft [lreplace $aft $i $i]
4395 lset rowidlist $row [concat $bef $pad $aft]
4399 proc optimize_rows {row col endrow} {
4400 global rowidlist rowisopt displayorder curview children
4405 for {} {$row < $endrow} {incr row; set col 0} {
4406 if {[lindex $rowisopt $row]} continue
4408 set y0 [expr {$row - 1}]
4409 set ym [expr {$row - 2}]
4410 set idlist [lindex $rowidlist $row]
4411 set previdlist [lindex $rowidlist $y0]
4412 if {$idlist eq {} || $previdlist eq {}} continue
4414 set pprevidlist [lindex $rowidlist $ym]
4415 if {$pprevidlist eq {}} continue
4421 for {} {$col < [llength $idlist]} {incr col} {
4422 set id [lindex $idlist $col]
4423 if {[lindex $previdlist $col] eq $id} continue
4428 set x0 [lsearch -exact $previdlist $id]
4429 if {$x0 < 0} continue
4430 set z [expr {$x0 - $col}]
4434 set xm [lsearch -exact $pprevidlist $id]
4436 set z0 [expr {$xm - $x0}]
4440 # if row y0 is the first child of $id then it's not an arrow
4441 if {[lindex $children($curview,$id) 0] ne
4442 [lindex $displayorder $y0]} {
4446 if {!$isarrow && $id ne [lindex $displayorder $row] &&
4447 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
4450 # Looking at lines from this row to the previous row,
4451 # make them go straight up if they end in an arrow on
4452 # the previous row; otherwise make them go straight up
4454 if {$z < -1 || ($z < 0 && $isarrow)} {
4455 # Line currently goes left too much;
4456 # insert pads in the previous row, then optimize it
4457 set npad [expr {-1 - $z + $isarrow}]
4458 insert_pad $y0 $x0 $npad
4460 optimize_rows $y0 $x0 $row
4462 set previdlist [lindex $rowidlist $y0]
4463 set x0 [lsearch -exact $previdlist $id]
4464 set z [expr {$x0 - $col}]
4466 set pprevidlist [lindex $rowidlist $ym]
4467 set xm [lsearch -exact $pprevidlist $id]
4468 set z0 [expr {$xm - $x0}]
4470 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
4471 # Line currently goes right too much;
4472 # insert pads in this line
4473 set npad [expr {$z - 1 + $isarrow}]
4474 insert_pad $row $col $npad
4475 set idlist [lindex $rowidlist $row]
4477 set z [expr {$x0 - $col}]
4480 if {$z0 eq {} && !$isarrow && $ym >= 0} {
4481 # this line links to its first child on row $row-2
4482 set id [lindex $displayorder $ym]
4483 set xc [lsearch -exact $pprevidlist $id]
4485 set z0 [expr {$xc - $x0}]
4488 # avoid lines jigging left then immediately right
4489 if {$z0 ne {} && $z < 0 && $z0 > 0} {
4490 insert_pad $y0 $x0 1
4492 optimize_rows $y0 $x0 $row
4493 set previdlist [lindex $rowidlist $y0]
4497 # Find the first column that doesn't have a line going right
4498 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
4499 set id [lindex $idlist $col]
4500 if {$id eq {}} break
4501 set x0 [lsearch -exact $previdlist $id]
4503 # check if this is the link to the first child
4504 set kid [lindex $displayorder $y0]
4505 if {[lindex $children($curview,$id) 0] eq $kid} {
4506 # it is, work out offset to child
4507 set x0 [lsearch -exact $previdlist $kid]
4510 if {$x0 <= $col} break
4512 # Insert a pad at that column as long as it has a line and
4513 # isn't the last column
4514 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
4515 set idlist [linsert $idlist $col {}]
4516 lset rowidlist $row $idlist
4524 global canvx0 linespc
4525 return [expr {$canvx0 + $col * $linespc}]
4529 global canvy0 linespc
4530 return [expr {$canvy0 + $row * $linespc}]
4533 proc linewidth {id} {
4534 global thickerline lthickness
4537 if {[info exists thickerline] && $id eq $thickerline} {
4538 set wid [expr {2 * $lthickness}]
4543 proc rowranges {id} {
4544 global curview children uparrowlen downarrowlen
4547 set kids $children($curview,$id)
4553 foreach child $kids {
4554 if {![commitinview $child $curview]} break
4555 set row [rowofcommit $child]
4556 if {![info exists prev]} {
4557 lappend ret [expr {$row + 1}]
4559 if {$row <= $prevrow} {
4560 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4562 # see if the line extends the whole way from prevrow to row
4563 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4564 [lsearch -exact [lindex $rowidlist \
4565 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4566 # it doesn't, see where it ends
4567 set r [expr {$prevrow + $downarrowlen}]
4568 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4569 while {[incr r -1] > $prevrow &&
4570 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4572 while {[incr r] <= $row &&
4573 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4577 # see where it starts up again
4578 set r [expr {$row - $uparrowlen}]
4579 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4580 while {[incr r] < $row &&
4581 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4583 while {[incr r -1] >= $prevrow &&
4584 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4590 if {$child eq $id} {
4599 proc drawlineseg {id row endrow arrowlow} {
4600 global rowidlist displayorder iddrawn linesegs
4601 global canv colormap linespc curview maxlinelen parentlist
4603 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4604 set le [expr {$row + 1}]
4607 set c [lsearch -exact [lindex $rowidlist $le] $id]
4613 set x [lindex $displayorder $le]
4618 if {[info exists iddrawn($x)] || $le == $endrow} {
4619 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
4635 if {[info exists linesegs($id)]} {
4636 set lines $linesegs($id)
4638 set r0 [lindex $li 0]
4640 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
4650 set li [lindex $lines [expr {$i-1}]]
4651 set r1 [lindex $li 1]
4652 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
4657 set x [lindex $cols [expr {$le - $row}]]
4658 set xp [lindex $cols [expr {$le - 1 - $row}]]
4659 set dir [expr {$xp - $x}]
4661 set ith [lindex $lines $i 2]
4662 set coords [$canv coords $ith]
4663 set ah [$canv itemcget $ith -arrow]
4664 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
4665 set x2 [lindex $cols [expr {$le + 1 - $row}]]
4666 if {$x2 ne {} && $x - $x2 == $dir} {
4667 set coords [lrange $coords 0 end-2]
4670 set coords [list [xc $le $x] [yc $le]]
4673 set itl [lindex $lines [expr {$i-1}] 2]
4674 set al [$canv itemcget $itl -arrow]
4675 set arrowlow [expr {$al eq "last" || $al eq "both"}]
4676 } elseif {$arrowlow} {
4677 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
4678 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
4682 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
4683 for {set y $le} {[incr y -1] > $row} {} {
4685 set xp [lindex $cols [expr {$y - 1 - $row}]]
4686 set ndir [expr {$xp - $x}]
4687 if {$dir != $ndir || $xp < 0} {
4688 lappend coords [xc $y $x] [yc $y]
4694 # join parent line to first child
4695 set ch [lindex $displayorder $row]
4696 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
4698 puts "oops: drawlineseg: child $ch not on row $row"
4699 } elseif {$xc != $x} {
4700 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
4701 set d [expr {int(0.5 * $linespc)}]
4704 set x2 [expr {$x1 - $d}]
4706 set x2 [expr {$x1 + $d}]
4709 set y1 [expr {$y2 + $d}]
4710 lappend coords $x1 $y1 $x2 $y2
4711 } elseif {$xc < $x - 1} {
4712 lappend coords [xc $row [expr {$x-1}]] [yc $row]
4713 } elseif {$xc > $x + 1} {
4714 lappend coords [xc $row [expr {$x+1}]] [yc $row]
4718 lappend coords [xc $row $x] [yc $row]
4720 set xn [xc $row $xp]
4722 lappend coords $xn $yn
4726 set t [$canv create line $coords -width [linewidth $id] \
4727 -fill $colormap($id) -tags lines.$id -arrow $arrow]
4730 set lines [linsert $lines $i [list $row $le $t]]
4732 $canv coords $ith $coords
4733 if {$arrow ne $ah} {
4734 $canv itemconf $ith -arrow $arrow
4736 lset lines $i 0 $row
4739 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
4740 set ndir [expr {$xo - $xp}]
4741 set clow [$canv coords $itl]
4742 if {$dir == $ndir} {
4743 set clow [lrange $clow 2 end]
4745 set coords [concat $coords $clow]
4747 lset lines [expr {$i-1}] 1 $le
4749 # coalesce two pieces
4751 set b [lindex $lines [expr {$i-1}] 0]
4752 set e [lindex $lines $i 1]
4753 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
4755 $canv coords $itl $coords
4756 if {$arrow ne $al} {
4757 $canv itemconf $itl -arrow $arrow
4761 set linesegs($id) $lines
4765 proc drawparentlinks {id row} {
4766 global rowidlist canv colormap curview parentlist
4767 global idpos linespc
4769 set rowids [lindex $rowidlist $row]
4770 set col [lsearch -exact $rowids $id]
4771 if {$col < 0} return
4772 set olds [lindex $parentlist $row]
4773 set row2 [expr {$row + 1}]
4774 set x [xc $row $col]
4777 set d [expr {int(0.5 * $linespc)}]
4778 set ymid [expr {$y + $d}]
4779 set ids [lindex $rowidlist $row2]
4780 # rmx = right-most X coord used
4783 set i [lsearch -exact $ids $p]
4785 puts "oops, parent $p of $id not in list"
4788 set x2 [xc $row2 $i]
4792 set j [lsearch -exact $rowids $p]
4794 # drawlineseg will do this one for us
4798 # should handle duplicated parents here...
4799 set coords [list $x $y]
4801 # if attaching to a vertical segment, draw a smaller
4802 # slant for visual distinctness
4805 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
4807 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
4809 } elseif {$i < $col && $i < $j} {
4810 # segment slants towards us already
4811 lappend coords [xc $row $j] $y
4813 if {$i < $col - 1} {
4814 lappend coords [expr {$x2 + $linespc}] $y
4815 } elseif {$i > $col + 1} {
4816 lappend coords [expr {$x2 - $linespc}] $y
4818 lappend coords $x2 $y2
4821 lappend coords $x2 $y2
4823 set t [$canv create line $coords -width [linewidth $p] \
4824 -fill $colormap($p) -tags lines.$p]
4828 if {$rmx > [lindex $idpos($id) 1]} {
4829 lset idpos($id) 1 $rmx
4834 proc drawlines {id} {
4837 $canv itemconf lines.$id -width [linewidth $id]
4840 proc drawcmittext {id row col} {
4841 global linespc canv canv2 canv3 fgcolor curview
4842 global cmitlisted commitinfo rowidlist parentlist
4843 global rowtextx idpos idtags idheads idotherrefs
4844 global linehtag linentag linedtag selectedline
4845 global canvxmax boldrows boldnamerows fgcolor nullid nullid2
4847 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
4848 set listed $cmitlisted($curview,$id)
4849 if {$id eq $nullid} {
4851 } elseif {$id eq $nullid2} {
4854 set ofill [expr {$listed != 0 ? $listed == 2 ? "gray" : "blue" : "white"}]
4856 set x [xc $row $col]
4858 set orad [expr {$linespc / 3}]
4860 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
4861 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4862 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4863 } elseif {$listed == 3} {
4864 # triangle pointing left for left-side commits
4865 set t [$canv create polygon \
4866 [expr {$x - $orad}] $y \
4867 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
4868 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4869 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4871 # triangle pointing right for right-side commits
4872 set t [$canv create polygon \
4873 [expr {$x + $orad - 1}] $y \
4874 [expr {$x - $orad}] [expr {$y - $orad}] \
4875 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
4876 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4879 $canv bind $t <1> {selcanvline {} %x %y}
4880 set rmx [llength [lindex $rowidlist $row]]
4881 set olds [lindex $parentlist $row]
4883 set nextids [lindex $rowidlist [expr {$row + 1}]]
4885 set i [lsearch -exact $nextids $p]
4891 set xt [xc $row $rmx]
4892 set rowtextx($row) $xt
4893 set idpos($id) [list $x $xt $y]
4894 if {[info exists idtags($id)] || [info exists idheads($id)]
4895 || [info exists idotherrefs($id)]} {
4896 set xt [drawtags $id $x $xt $y]
4898 set headline [lindex $commitinfo($id) 0]
4899 set name [lindex $commitinfo($id) 1]
4900 set date [lindex $commitinfo($id) 2]
4901 set date [formatdate $date]
4904 set isbold [ishighlighted $id]
4906 lappend boldrows $row
4907 set font mainfontbold
4909 lappend boldnamerows $row
4910 set nfont mainfontbold
4913 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
4914 -text $headline -font $font -tags text]
4915 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
4916 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
4917 -text $name -font $nfont -tags text]
4918 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
4919 -text $date -font mainfont -tags text]
4920 if {[info exists selectedline] && $selectedline == $row} {
4923 set xr [expr {$xt + [font measure $font $headline]}]
4924 if {$xr > $canvxmax} {
4930 proc drawcmitrow {row} {
4931 global displayorder rowidlist nrows_drawn
4932 global iddrawn markingmatches
4933 global commitinfo numcommits
4934 global filehighlight fhighlights findpattern nhighlights
4935 global hlview vhighlights
4936 global highlight_related rhighlights
4938 if {$row >= $numcommits} return
4940 set id [lindex $displayorder $row]
4941 if {[info exists hlview] && ![info exists vhighlights($id)]} {
4942 askvhighlight $row $id
4944 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
4945 askfilehighlight $row $id
4947 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
4948 askfindhighlight $row $id
4950 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
4951 askrelhighlight $row $id
4953 if {![info exists iddrawn($id)]} {
4954 set col [lsearch -exact [lindex $rowidlist $row] $id]
4956 puts "oops, row $row id $id not in list"
4959 if {![info exists commitinfo($id)]} {
4963 drawcmittext $id $row $col
4967 if {$markingmatches} {
4968 markrowmatches $row $id
4972 proc drawcommits {row {endrow {}}} {
4973 global numcommits iddrawn displayorder curview need_redisplay
4974 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
4979 if {$endrow eq {}} {
4982 if {$endrow >= $numcommits} {
4983 set endrow [expr {$numcommits - 1}]
4986 set rl1 [expr {$row - $downarrowlen - 3}]
4990 set ro1 [expr {$row - 3}]
4994 set r2 [expr {$endrow + $uparrowlen + 3}]
4995 if {$r2 > $numcommits} {
4998 for {set r $rl1} {$r < $r2} {incr r} {
4999 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5003 set rl1 [expr {$r + 1}]
5009 optimize_rows $ro1 0 $r2
5010 if {$need_redisplay || $nrows_drawn > 2000} {
5015 # make the lines join to already-drawn rows either side
5016 set r [expr {$row - 1}]
5017 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5020 set er [expr {$endrow + 1}]
5021 if {$er >= $numcommits ||
5022 ![info exists iddrawn([lindex $displayorder $er])]} {
5025 for {} {$r <= $er} {incr r} {
5026 set id [lindex $displayorder $r]
5027 set wasdrawn [info exists iddrawn($id)]
5029 if {$r == $er} break
5030 set nextid [lindex $displayorder [expr {$r + 1}]]
5031 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5032 drawparentlinks $id $r
5034 set rowids [lindex $rowidlist $r]
5035 foreach lid $rowids {
5036 if {$lid eq {}} continue
5037 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5039 # see if this is the first child of any of its parents
5040 foreach p [lindex $parentlist $r] {
5041 if {[lsearch -exact $rowids $p] < 0} {
5042 # make this line extend up to the child
5043 set lineend($p) [drawlineseg $p $r $er 0]
5047 set lineend($lid) [drawlineseg $lid $r $er 1]
5053 proc undolayout {row} {
5054 global uparrowlen mingaplen downarrowlen
5055 global rowidlist rowisopt rowfinal need_redisplay
5057 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5061 if {[llength $rowidlist] > $r} {
5063 set rowidlist [lrange $rowidlist 0 $r]
5064 set rowfinal [lrange $rowfinal 0 $r]
5065 set rowisopt [lrange $rowisopt 0 $r]
5066 set need_redisplay 1
5071 proc drawvisible {} {
5072 global canv linespc curview vrowmod selectedline targetrow targetid
5073 global need_redisplay cscroll numcommits
5075 set fs [$canv yview]
5076 set ymax [lindex [$canv cget -scrollregion] 3]
5077 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5078 set f0 [lindex $fs 0]
5079 set f1 [lindex $fs 1]
5080 set y0 [expr {int($f0 * $ymax)}]
5081 set y1 [expr {int($f1 * $ymax)}]
5083 if {[info exists targetid]} {
5084 if {[commitinview $targetid $curview]} {
5085 set r [rowofcommit $targetid]
5086 if {$r != $targetrow} {
5087 # Fix up the scrollregion and change the scrolling position
5088 # now that our target row has moved.
5089 set diff [expr {($r - $targetrow) * $linespc}]
5092 set ymax [lindex [$canv cget -scrollregion] 3]
5095 set f0 [expr {$y0 / $ymax}]
5096 set f1 [expr {$y1 / $ymax}]
5097 allcanvs yview moveto $f0
5098 $cscroll set $f0 $f1
5099 set need_redisplay 1
5106 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5107 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5108 if {$endrow >= $vrowmod($curview)} {
5109 update_arcrows $curview
5111 if {[info exists selectedline] &&
5112 $row <= $selectedline && $selectedline <= $endrow} {
5113 set targetrow $selectedline
5114 } elseif {[info exists targetid]} {
5115 set targetrow [expr {int(($row + $endrow) / 2)}]
5117 if {[info exists targetrow]} {
5118 if {$targetrow >= $numcommits} {
5119 set targetrow [expr {$numcommits - 1}]
5121 set targetid [commitonrow $targetrow]
5123 drawcommits $row $endrow
5126 proc clear_display {} {
5127 global iddrawn linesegs need_redisplay nrows_drawn
5128 global vhighlights fhighlights nhighlights rhighlights
5131 catch {unset iddrawn}
5132 catch {unset linesegs}
5133 catch {unset vhighlights}
5134 catch {unset fhighlights}
5135 catch {unset nhighlights}
5136 catch {unset rhighlights}
5137 set need_redisplay 0
5141 proc findcrossings {id} {
5142 global rowidlist parentlist numcommits displayorder
5146 foreach {s e} [rowranges $id] {
5147 if {$e >= $numcommits} {
5148 set e [expr {$numcommits - 1}]
5150 if {$e <= $s} continue
5151 for {set row $e} {[incr row -1] >= $s} {} {
5152 set x [lsearch -exact [lindex $rowidlist $row] $id]
5154 set olds [lindex $parentlist $row]
5155 set kid [lindex $displayorder $row]
5156 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5157 if {$kidx < 0} continue
5158 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5160 set px [lsearch -exact $nextrow $p]
5161 if {$px < 0} continue
5162 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5163 if {[lsearch -exact $ccross $p] >= 0} continue
5164 if {$x == $px + ($kidx < $px? -1: 1)} {
5166 } elseif {[lsearch -exact $cross $p] < 0} {
5173 return [concat $ccross {{}} $cross]
5176 proc assigncolor {id} {
5177 global colormap colors nextcolor
5178 global parents children children curview
5180 if {[info exists colormap($id)]} return
5181 set ncolors [llength $colors]
5182 if {[info exists children($curview,$id)]} {
5183 set kids $children($curview,$id)
5187 if {[llength $kids] == 1} {
5188 set child [lindex $kids 0]
5189 if {[info exists colormap($child)]
5190 && [llength $parents($curview,$child)] == 1} {
5191 set colormap($id) $colormap($child)
5197 foreach x [findcrossings $id] {
5199 # delimiter between corner crossings and other crossings
5200 if {[llength $badcolors] >= $ncolors - 1} break
5201 set origbad $badcolors
5203 if {[info exists colormap($x)]
5204 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5205 lappend badcolors $colormap($x)
5208 if {[llength $badcolors] >= $ncolors} {
5209 set badcolors $origbad
5211 set origbad $badcolors
5212 if {[llength $badcolors] < $ncolors - 1} {
5213 foreach child $kids {
5214 if {[info exists colormap($child)]
5215 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5216 lappend badcolors $colormap($child)
5218 foreach p $parents($curview,$child) {
5219 if {[info exists colormap($p)]
5220 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5221 lappend badcolors $colormap($p)
5225 if {[llength $badcolors] >= $ncolors} {
5226 set badcolors $origbad
5229 for {set i 0} {$i <= $ncolors} {incr i} {
5230 set c [lindex $colors $nextcolor]
5231 if {[incr nextcolor] >= $ncolors} {
5234 if {[lsearch -exact $badcolors $c]} break
5236 set colormap($id) $c
5239 proc bindline {t id} {
5242 $canv bind $t <Enter> "lineenter %x %y $id"
5243 $canv bind $t <Motion> "linemotion %x %y $id"
5244 $canv bind $t <Leave> "lineleave $id"
5245 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5248 proc drawtags {id x xt y1} {
5249 global idtags idheads idotherrefs mainhead
5250 global linespc lthickness
5251 global canv rowtextx curview fgcolor bgcolor
5256 if {[info exists idtags($id)]} {
5257 set marks $idtags($id)
5258 set ntags [llength $marks]
5260 if {[info exists idheads($id)]} {
5261 set marks [concat $marks $idheads($id)]
5262 set nheads [llength $idheads($id)]
5264 if {[info exists idotherrefs($id)]} {
5265 set marks [concat $marks $idotherrefs($id)]
5271 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5272 set yt [expr {$y1 - 0.5 * $linespc}]
5273 set yb [expr {$yt + $linespc - 1}]
5277 foreach tag $marks {
5279 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5280 set wid [font measure mainfontbold $tag]
5282 set wid [font measure mainfont $tag]
5286 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5288 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5289 -width $lthickness -fill black -tags tag.$id]
5291 foreach tag $marks x $xvals wid $wvals {
5292 set xl [expr {$x + $delta}]
5293 set xr [expr {$x + $delta + $wid + $lthickness}]
5295 if {[incr ntags -1] >= 0} {
5297 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5298 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5299 -width 1 -outline black -fill yellow -tags tag.$id]
5300 $canv bind $t <1> [list showtag $tag 1]
5301 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5303 # draw a head or other ref
5304 if {[incr nheads -1] >= 0} {
5306 if {$tag eq $mainhead} {
5307 set font mainfontbold
5312 set xl [expr {$xl - $delta/2}]
5313 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5314 -width 1 -outline black -fill $col -tags tag.$id
5315 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5316 set rwid [font measure mainfont $remoteprefix]
5317 set xi [expr {$x + 1}]
5318 set yti [expr {$yt + 1}]
5319 set xri [expr {$x + $rwid}]
5320 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5321 -width 0 -fill "#ffddaa" -tags tag.$id
5324 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5325 -font $font -tags [list tag.$id text]]
5327 $canv bind $t <1> [list showtag $tag 1]
5328 } elseif {$nheads >= 0} {
5329 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
5335 proc xcoord {i level ln} {
5336 global canvx0 xspc1 xspc2
5338 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5339 if {$i > 0 && $i == $level} {
5340 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5341 } elseif {$i > $level} {
5342 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5347 proc show_status {msg} {
5351 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5352 -tags text -fill $fgcolor
5355 # Don't change the text pane cursor if it is currently the hand cursor,
5356 # showing that we are over a sha1 ID link.
5357 proc settextcursor {c} {
5358 global ctext curtextcursor
5360 if {[$ctext cget -cursor] == $curtextcursor} {
5361 $ctext config -cursor $c
5363 set curtextcursor $c
5366 proc nowbusy {what {name {}}} {
5367 global isbusy busyname statusw
5369 if {[array names isbusy] eq {}} {
5370 . config -cursor watch
5374 set busyname($what) $name
5376 $statusw conf -text $name
5380 proc notbusy {what} {
5381 global isbusy maincursor textcursor busyname statusw
5385 if {$busyname($what) ne {} &&
5386 [$statusw cget -text] eq $busyname($what)} {
5387 $statusw conf -text {}
5390 if {[array names isbusy] eq {}} {
5391 . config -cursor $maincursor
5392 settextcursor $textcursor
5396 proc findmatches {f} {
5397 global findtype findstring
5398 if {$findtype == [mc "Regexp"]} {
5399 set matches [regexp -indices -all -inline $findstring $f]
5402 if {$findtype == [mc "IgnCase"]} {
5403 set f [string tolower $f]
5404 set fs [string tolower $fs]
5408 set l [string length $fs]
5409 while {[set j [string first $fs $f $i]] >= 0} {
5410 lappend matches [list $j [expr {$j+$l-1}]]
5411 set i [expr {$j + $l}]
5417 proc dofind {{dirn 1} {wrap 1}} {
5418 global findstring findstartline findcurline selectedline numcommits
5419 global gdttype filehighlight fh_serial find_dirn findallowwrap
5421 if {[info exists find_dirn]} {
5422 if {$find_dirn == $dirn} return
5426 if {$findstring eq {} || $numcommits == 0} return
5427 if {![info exists selectedline]} {
5428 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
5430 set findstartline $selectedline
5432 set findcurline $findstartline
5433 nowbusy finding [mc "Searching"]
5434 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
5435 after cancel do_file_hl $fh_serial
5436 do_file_hl $fh_serial
5439 set findallowwrap $wrap
5443 proc stopfinding {} {
5444 global find_dirn findcurline fprogcoord
5446 if {[info exists find_dirn]} {
5456 global commitdata commitinfo numcommits findpattern findloc
5457 global findstartline findcurline findallowwrap
5458 global find_dirn gdttype fhighlights fprogcoord
5459 global curview varcorder vrownum varccommits vrowmod
5461 if {![info exists find_dirn]} {
5464 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
5467 if {$find_dirn > 0} {
5469 if {$l >= $numcommits} {
5472 if {$l <= $findstartline} {
5473 set lim [expr {$findstartline + 1}]
5476 set moretodo $findallowwrap
5483 if {$l >= $findstartline} {
5484 set lim [expr {$findstartline - 1}]
5487 set moretodo $findallowwrap
5490 set n [expr {($lim - $l) * $find_dirn}]
5495 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
5496 update_arcrows $curview
5500 set ai [bsearch $vrownum($curview) $l]
5501 set a [lindex $varcorder($curview) $ai]
5502 set arow [lindex $vrownum($curview) $ai]
5503 set ids [lindex $varccommits($curview,$a)]
5504 set arowend [expr {$arow + [llength $ids]}]
5505 if {$gdttype eq [mc "containing:"]} {
5506 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5507 if {$l < $arow || $l >= $arowend} {
5509 set a [lindex $varcorder($curview) $ai]
5510 set arow [lindex $vrownum($curview) $ai]
5511 set ids [lindex $varccommits($curview,$a)]
5512 set arowend [expr {$arow + [llength $ids]}]
5514 set id [lindex $ids [expr {$l - $arow}]]
5515 # shouldn't happen unless git log doesn't give all the commits...
5516 if {![info exists commitdata($id)] ||
5517 ![doesmatch $commitdata($id)]} {
5520 if {![info exists commitinfo($id)]} {
5523 set info $commitinfo($id)
5524 foreach f $info ty $fldtypes {
5525 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
5534 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5535 if {$l < $arow || $l >= $arowend} {
5537 set a [lindex $varcorder($curview) $ai]
5538 set arow [lindex $vrownum($curview) $ai]
5539 set ids [lindex $varccommits($curview,$a)]
5540 set arowend [expr {$arow + [llength $ids]}]
5542 set id [lindex $ids [expr {$l - $arow}]]
5543 if {![info exists fhighlights($id)]} {
5544 # this sets fhighlights($id) to -1
5545 askfilehighlight $l $id
5547 if {$fhighlights($id) > 0} {
5551 if {$fhighlights($id) < 0} {
5554 set findcurline [expr {$l - $find_dirn}]
5559 if {$found || ($domore && !$moretodo)} {
5575 set findcurline [expr {$l - $find_dirn}]
5577 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
5581 set fprogcoord [expr {$n * 1.0 / $numcommits}]
5586 proc findselectline {l} {
5587 global findloc commentend ctext findcurline markingmatches gdttype
5589 set markingmatches 1
5592 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
5593 # highlight the matches in the comments
5594 set f [$ctext get 1.0 $commentend]
5595 set matches [findmatches $f]
5596 foreach match $matches {
5597 set start [lindex $match 0]
5598 set end [expr {[lindex $match 1] + 1}]
5599 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5605 # mark the bits of a headline or author that match a find string
5606 proc markmatches {canv l str tag matches font row} {
5609 set bbox [$canv bbox $tag]
5610 set x0 [lindex $bbox 0]
5611 set y0 [lindex $bbox 1]
5612 set y1 [lindex $bbox 3]
5613 foreach match $matches {
5614 set start [lindex $match 0]
5615 set end [lindex $match 1]
5616 if {$start > $end} continue
5617 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
5618 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
5619 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
5620 [expr {$x0+$xlen+2}] $y1 \
5621 -outline {} -tags [list match$l matches] -fill yellow]
5623 if {[info exists selectedline] && $row == $selectedline} {
5624 $canv raise $t secsel
5629 proc unmarkmatches {} {
5630 global markingmatches
5632 allcanvs delete matches
5633 set markingmatches 0
5637 proc selcanvline {w x y} {
5638 global canv canvy0 ctext linespc
5640 set ymax [lindex [$canv cget -scrollregion] 3]
5641 if {$ymax == {}} return
5642 set yfrac [lindex [$canv yview] 0]
5643 set y [expr {$y + $yfrac * $ymax}]
5644 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
5649 set xmax [lindex [$canv cget -scrollregion] 2]
5650 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
5651 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
5657 proc commit_descriptor {p} {
5659 if {![info exists commitinfo($p)]} {
5663 if {[llength $commitinfo($p)] > 1} {
5664 set l [lindex $commitinfo($p) 0]
5669 # append some text to the ctext widget, and make any SHA1 ID
5670 # that we know about be a clickable link.
5671 proc appendwithlinks {text tags} {
5672 global ctext linknum curview pendinglinks
5674 set start [$ctext index "end - 1c"]
5675 $ctext insert end $text $tags
5676 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
5680 set linkid [string range $text $s $e]
5682 $ctext tag delete link$linknum
5683 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
5684 setlink $linkid link$linknum
5689 proc setlink {id lk} {
5690 global curview ctext pendinglinks commitinterest
5692 if {[commitinview $id $curview]} {
5693 $ctext tag conf $lk -foreground blue -underline 1
5694 $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1]
5695 $ctext tag bind $lk <Enter> {linkcursor %W 1}
5696 $ctext tag bind $lk <Leave> {linkcursor %W -1}
5698 lappend pendinglinks($id) $lk
5699 lappend commitinterest($id) {makelink %I}
5703 proc makelink {id} {
5706 if {![info exists pendinglinks($id)]} return
5707 foreach lk $pendinglinks($id) {
5710 unset pendinglinks($id)
5713 proc linkcursor {w inc} {
5714 global linkentercount curtextcursor
5716 if {[incr linkentercount $inc] > 0} {
5717 $w configure -cursor hand2
5719 $w configure -cursor $curtextcursor
5720 if {$linkentercount < 0} {
5721 set linkentercount 0
5726 proc viewnextline {dir} {
5730 set ymax [lindex [$canv cget -scrollregion] 3]
5731 set wnow [$canv yview]
5732 set wtop [expr {[lindex $wnow 0] * $ymax}]
5733 set newtop [expr {$wtop + $dir * $linespc}]
5736 } elseif {$newtop > $ymax} {
5739 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5742 # add a list of tag or branch names at position pos
5743 # returns the number of names inserted
5744 proc appendrefs {pos ids var} {
5745 global ctext linknum curview $var maxrefs
5747 if {[catch {$ctext index $pos}]} {
5750 $ctext conf -state normal
5751 $ctext delete $pos "$pos lineend"
5754 foreach tag [set $var\($id\)] {
5755 lappend tags [list $tag $id]
5758 if {[llength $tags] > $maxrefs} {
5759 $ctext insert $pos "many ([llength $tags])"
5761 set tags [lsort -index 0 -decreasing $tags]
5764 set id [lindex $ti 1]
5767 $ctext tag delete $lk
5768 $ctext insert $pos $sep
5769 $ctext insert $pos [lindex $ti 0] $lk
5774 $ctext conf -state disabled
5775 return [llength $tags]
5778 # called when we have finished computing the nearby tags
5779 proc dispneartags {delay} {
5780 global selectedline currentid showneartags tagphase
5782 if {![info exists selectedline] || !$showneartags} return
5783 after cancel dispnexttag
5785 after 200 dispnexttag
5788 after idle dispnexttag
5793 proc dispnexttag {} {
5794 global selectedline currentid showneartags tagphase ctext
5796 if {![info exists selectedline] || !$showneartags} return
5797 switch -- $tagphase {
5799 set dtags [desctags $currentid]
5801 appendrefs precedes $dtags idtags
5805 set atags [anctags $currentid]
5807 appendrefs follows $atags idtags
5811 set dheads [descheads $currentid]
5812 if {$dheads ne {}} {
5813 if {[appendrefs branch $dheads idheads] > 1
5814 && [$ctext get "branch -3c"] eq "h"} {
5815 # turn "Branch" into "Branches"
5816 $ctext conf -state normal
5817 $ctext insert "branch -2c" "es"
5818 $ctext conf -state disabled
5823 if {[incr tagphase] <= 2} {
5824 after idle dispnexttag
5828 proc make_secsel {l} {
5829 global linehtag linentag linedtag canv canv2 canv3
5831 if {![info exists linehtag($l)]} return
5833 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
5834 -tags secsel -fill [$canv cget -selectbackground]]
5836 $canv2 delete secsel
5837 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
5838 -tags secsel -fill [$canv2 cget -selectbackground]]
5840 $canv3 delete secsel
5841 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
5842 -tags secsel -fill [$canv3 cget -selectbackground]]
5846 proc selectline {l isnew} {
5847 global canv ctext commitinfo selectedline
5848 global canvy0 linespc parents children curview
5849 global currentid sha1entry
5850 global commentend idtags linknum
5851 global mergemax numcommits pending_select
5852 global cmitmode showneartags allcommits
5853 global targetrow targetid lastscrollrows
5856 catch {unset pending_select}
5861 if {$l < 0 || $l >= $numcommits} return
5862 set id [commitonrow $l]
5867 if {$lastscrollrows < $numcommits} {
5871 set y [expr {$canvy0 + $l * $linespc}]
5872 set ymax [lindex [$canv cget -scrollregion] 3]
5873 set ytop [expr {$y - $linespc - 1}]
5874 set ybot [expr {$y + $linespc + 1}]
5875 set wnow [$canv yview]
5876 set wtop [expr {[lindex $wnow 0] * $ymax}]
5877 set wbot [expr {[lindex $wnow 1] * $ymax}]
5878 set wh [expr {$wbot - $wtop}]
5880 if {$ytop < $wtop} {
5881 if {$ybot < $wtop} {
5882 set newtop [expr {$y - $wh / 2.0}]
5885 if {$newtop > $wtop - $linespc} {
5886 set newtop [expr {$wtop - $linespc}]
5889 } elseif {$ybot > $wbot} {
5890 if {$ytop > $wbot} {
5891 set newtop [expr {$y - $wh / 2.0}]
5893 set newtop [expr {$ybot - $wh}]
5894 if {$newtop < $wtop + $linespc} {
5895 set newtop [expr {$wtop + $linespc}]
5899 if {$newtop != $wtop} {
5903 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5910 addtohistory [list selbyid $id]
5913 $sha1entry delete 0 end
5914 $sha1entry insert 0 $id
5916 $sha1entry selection from 0
5917 $sha1entry selection to end
5921 $ctext conf -state normal
5924 if {![info exists commitinfo($id)]} {
5927 set info $commitinfo($id)
5928 set date [formatdate [lindex $info 2]]
5929 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
5930 set date [formatdate [lindex $info 4]]
5931 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
5932 if {[info exists idtags($id)]} {
5933 $ctext insert end [mc "Tags:"]
5934 foreach tag $idtags($id) {
5935 $ctext insert end " $tag"
5937 $ctext insert end "\n"
5941 set olds $parents($curview,$id)
5942 if {[llength $olds] > 1} {
5945 if {$np >= $mergemax} {
5950 $ctext insert end "[mc "Parent"]: " $tag
5951 appendwithlinks [commit_descriptor $p] {}
5956 append headers "[mc "Parent"]: [commit_descriptor $p]"
5960 foreach c $children($curview,$id) {
5961 append headers "[mc "Child"]: [commit_descriptor $c]"
5964 # make anything that looks like a SHA1 ID be a clickable link
5965 appendwithlinks $headers {}
5966 if {$showneartags} {
5967 if {![info exists allcommits]} {
5970 $ctext insert end "[mc "Branch"]: "
5971 $ctext mark set branch "end -1c"
5972 $ctext mark gravity branch left
5973 $ctext insert end "\n[mc "Follows"]: "
5974 $ctext mark set follows "end -1c"
5975 $ctext mark gravity follows left
5976 $ctext insert end "\n[mc "Precedes"]: "
5977 $ctext mark set precedes "end -1c"
5978 $ctext mark gravity precedes left
5979 $ctext insert end "\n"
5982 $ctext insert end "\n"
5983 set comment [lindex $info 5]
5984 if {[string first "\r" $comment] >= 0} {
5985 set comment [string map {"\r" "\n "} $comment]
5987 appendwithlinks $comment {comment}
5989 $ctext tag remove found 1.0 end
5990 $ctext conf -state disabled
5991 set commentend [$ctext index "end - 1c"]
5993 init_flist [mc "Comments"]
5994 if {$cmitmode eq "tree"} {
5996 } elseif {[llength $olds] <= 1} {
6003 proc selfirstline {} {
6008 proc sellastline {} {
6011 set l [expr {$numcommits - 1}]
6015 proc selnextline {dir} {
6018 if {![info exists selectedline]} return
6019 set l [expr {$selectedline + $dir}]
6024 proc selnextpage {dir} {
6025 global canv linespc selectedline numcommits
6027 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6031 allcanvs yview scroll [expr {$dir * $lpp}] units
6033 if {![info exists selectedline]} return
6034 set l [expr {$selectedline + $dir * $lpp}]
6037 } elseif {$l >= $numcommits} {
6038 set l [expr $numcommits - 1]
6044 proc unselectline {} {
6045 global selectedline currentid
6047 catch {unset selectedline}
6048 catch {unset currentid}
6049 allcanvs delete secsel
6053 proc reselectline {} {
6056 if {[info exists selectedline]} {
6057 selectline $selectedline 0
6061 proc addtohistory {cmd} {
6062 global history historyindex curview
6064 set elt [list $curview $cmd]
6065 if {$historyindex > 0
6066 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6070 if {$historyindex < [llength $history]} {
6071 set history [lreplace $history $historyindex end $elt]
6073 lappend history $elt
6076 if {$historyindex > 1} {
6077 .tf.bar.leftbut conf -state normal
6079 .tf.bar.leftbut conf -state disabled
6081 .tf.bar.rightbut conf -state disabled
6087 set view [lindex $elt 0]
6088 set cmd [lindex $elt 1]
6089 if {$curview != $view} {
6096 global history historyindex
6099 if {$historyindex > 1} {
6100 incr historyindex -1
6101 godo [lindex $history [expr {$historyindex - 1}]]
6102 .tf.bar.rightbut conf -state normal
6104 if {$historyindex <= 1} {
6105 .tf.bar.leftbut conf -state disabled
6110 global history historyindex
6113 if {$historyindex < [llength $history]} {
6114 set cmd [lindex $history $historyindex]
6117 .tf.bar.leftbut conf -state normal
6119 if {$historyindex >= [llength $history]} {
6120 .tf.bar.rightbut conf -state disabled
6125 global treefilelist treeidlist diffids diffmergeid treepending
6126 global nullid nullid2
6129 catch {unset diffmergeid}
6130 if {![info exists treefilelist($id)]} {
6131 if {![info exists treepending]} {
6132 if {$id eq $nullid} {
6133 set cmd [list | git ls-files]
6134 } elseif {$id eq $nullid2} {
6135 set cmd [list | git ls-files --stage -t]
6137 set cmd [list | git ls-tree -r $id]
6139 if {[catch {set gtf [open $cmd r]}]} {
6143 set treefilelist($id) {}
6144 set treeidlist($id) {}
6145 fconfigure $gtf -blocking 0
6146 filerun $gtf [list gettreeline $gtf $id]
6153 proc gettreeline {gtf id} {
6154 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6157 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6158 if {$diffids eq $nullid} {
6161 set i [string first "\t" $line]
6162 if {$i < 0} continue
6163 set fname [string range $line [expr {$i+1}] end]
6164 set line [string range $line 0 [expr {$i-1}]]
6165 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6166 set sha1 [lindex $line 2]
6167 if {[string index $fname 0] eq "\""} {
6168 set fname [lindex $fname 0]
6170 lappend treeidlist($id) $sha1
6172 lappend treefilelist($id) $fname
6175 return [expr {$nl >= 1000? 2: 1}]
6179 if {$cmitmode ne "tree"} {
6180 if {![info exists diffmergeid]} {
6181 gettreediffs $diffids
6183 } elseif {$id ne $diffids} {
6192 global treefilelist treeidlist diffids nullid nullid2
6193 global ctext commentend
6195 set i [lsearch -exact $treefilelist($diffids) $f]
6197 puts "oops, $f not in list for id $diffids"
6200 if {$diffids eq $nullid} {
6201 if {[catch {set bf [open $f r]} err]} {
6202 puts "oops, can't read $f: $err"
6206 set blob [lindex $treeidlist($diffids) $i]
6207 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6208 puts "oops, error reading blob $blob: $err"
6212 fconfigure $bf -blocking 0
6213 filerun $bf [list getblobline $bf $diffids]
6214 $ctext config -state normal
6215 clear_ctext $commentend
6216 $ctext insert end "\n"
6217 $ctext insert end "$f\n" filesep
6218 $ctext config -state disabled
6219 $ctext yview $commentend
6223 proc getblobline {bf id} {
6224 global diffids cmitmode ctext
6226 if {$id ne $diffids || $cmitmode ne "tree"} {
6230 $ctext config -state normal
6232 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6233 $ctext insert end "$line\n"
6236 # delete last newline
6237 $ctext delete "end - 2c" "end - 1c"
6241 $ctext config -state disabled
6242 return [expr {$nl >= 1000? 2: 1}]
6245 proc mergediff {id} {
6246 global diffmergeid mdifffd
6250 global limitdiffs vfilelimit curview
6254 # this doesn't seem to actually affect anything...
6255 set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
6256 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6257 set cmd [concat $cmd -- $vfilelimit($curview)]
6259 if {[catch {set mdf [open $cmd r]} err]} {
6260 error_popup "[mc "Error getting merge diffs:"] $err"
6263 fconfigure $mdf -blocking 0
6264 set mdifffd($id) $mdf
6265 set np [llength $parents($curview,$id)]
6267 filerun $mdf [list getmergediffline $mdf $id $np]
6270 proc getmergediffline {mdf id np} {
6271 global diffmergeid ctext cflist mergemax
6272 global difffilestart mdifffd
6274 $ctext conf -state normal
6276 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
6277 if {![info exists diffmergeid] || $id != $diffmergeid
6278 || $mdf != $mdifffd($id)} {
6282 if {[regexp {^diff --cc (.*)} $line match fname]} {
6283 # start of a new file
6284 $ctext insert end "\n"
6285 set here [$ctext index "end - 1c"]
6286 lappend difffilestart $here
6287 add_flist [list $fname]
6288 set l [expr {(78 - [string length $fname]) / 2}]
6289 set pad [string range "----------------------------------------" 1 $l]
6290 $ctext insert end "$pad $fname $pad\n" filesep
6291 } elseif {[regexp {^@@} $line]} {
6292 $ctext insert end "$line\n" hunksep
6293 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
6296 # parse the prefix - one ' ', '-' or '+' for each parent
6301 for {set j 0} {$j < $np} {incr j} {
6302 set c [string range $line $j $j]
6305 } elseif {$c == "-"} {
6307 } elseif {$c == "+"} {
6316 if {!$isbad && $minuses ne {} && $pluses eq {}} {
6317 # line doesn't appear in result, parents in $minuses have the line
6318 set num [lindex $minuses 0]
6319 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
6320 # line appears in result, parents in $pluses don't have the line
6321 lappend tags mresult
6322 set num [lindex $spaces 0]
6325 if {$num >= $mergemax} {
6330 $ctext insert end "$line\n" $tags
6333 $ctext conf -state disabled
6338 return [expr {$nr >= 1000? 2: 1}]
6341 proc startdiff {ids} {
6342 global treediffs diffids treepending diffmergeid nullid nullid2
6346 catch {unset diffmergeid}
6347 if {![info exists treediffs($ids)] ||
6348 [lsearch -exact $ids $nullid] >= 0 ||
6349 [lsearch -exact $ids $nullid2] >= 0} {
6350 if {![info exists treepending]} {
6358 proc path_filter {filter name} {
6360 set l [string length $p]
6361 if {[string index $p end] eq "/"} {
6362 if {[string compare -length $l $p $name] == 0} {
6366 if {[string compare -length $l $p $name] == 0 &&
6367 ([string length $name] == $l ||
6368 [string index $name $l] eq "/")} {
6376 proc addtocflist {ids} {
6379 add_flist $treediffs($ids)
6383 proc diffcmd {ids flags} {
6384 global nullid nullid2
6386 set i [lsearch -exact $ids $nullid]
6387 set j [lsearch -exact $ids $nullid2]
6389 if {[llength $ids] > 1 && $j < 0} {
6390 # comparing working directory with some specific revision
6391 set cmd [concat | git diff-index $flags]
6393 lappend cmd -R [lindex $ids 1]
6395 lappend cmd [lindex $ids 0]
6398 # comparing working directory with index
6399 set cmd [concat | git diff-files $flags]
6404 } elseif {$j >= 0} {
6405 set cmd [concat | git diff-index --cached $flags]
6406 if {[llength $ids] > 1} {
6407 # comparing index with specific revision
6409 lappend cmd -R [lindex $ids 1]
6411 lappend cmd [lindex $ids 0]
6414 # comparing index with HEAD
6418 set cmd [concat | git diff-tree -r $flags $ids]
6423 proc gettreediffs {ids} {
6424 global treediff treepending
6426 set treepending $ids
6428 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6429 fconfigure $gdtf -blocking 0
6430 filerun $gdtf [list gettreediffline $gdtf $ids]
6433 proc gettreediffline {gdtf ids} {
6434 global treediff treediffs treepending diffids diffmergeid
6435 global cmitmode vfilelimit curview limitdiffs
6438 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
6439 set i [string first "\t" $line]
6441 set file [string range $line [expr {$i+1}] end]
6442 if {[string index $file 0] eq "\""} {
6443 set file [lindex $file 0]
6445 lappend treediff $file
6449 return [expr {$nr >= 1000? 2: 1}]
6452 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6454 foreach f $treediff {
6455 if {[path_filter $vfilelimit($curview) $f]} {
6459 set treediffs($ids) $flist
6461 set treediffs($ids) $treediff
6464 if {$cmitmode eq "tree"} {
6466 } elseif {$ids != $diffids} {
6467 if {![info exists diffmergeid]} {
6468 gettreediffs $diffids
6476 # empty string or positive integer
6477 proc diffcontextvalidate {v} {
6478 return [regexp {^(|[1-9][0-9]*)$} $v]
6481 proc diffcontextchange {n1 n2 op} {
6482 global diffcontextstring diffcontext
6484 if {[string is integer -strict $diffcontextstring]} {
6485 if {$diffcontextstring > 0} {
6486 set diffcontext $diffcontextstring
6492 proc changeignorespace {} {
6496 proc getblobdiffs {ids} {
6497 global blobdifffd diffids env
6498 global diffinhdr treediffs
6501 global limitdiffs vfilelimit curview
6503 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
6507 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6508 set cmd [concat $cmd -- $vfilelimit($curview)]
6510 if {[catch {set bdf [open $cmd r]} err]} {
6511 puts "error getting diffs: $err"
6515 fconfigure $bdf -blocking 0
6516 set blobdifffd($ids) $bdf
6517 filerun $bdf [list getblobdiffline $bdf $diffids]
6520 proc setinlist {var i val} {
6523 while {[llength [set $var]] < $i} {
6526 if {[llength [set $var]] == $i} {
6533 proc makediffhdr {fname ids} {
6534 global ctext curdiffstart treediffs
6536 set i [lsearch -exact $treediffs($ids) $fname]
6538 setinlist difffilestart $i $curdiffstart
6540 set l [expr {(78 - [string length $fname]) / 2}]
6541 set pad [string range "----------------------------------------" 1 $l]
6542 $ctext insert $curdiffstart "$pad $fname $pad" filesep
6545 proc getblobdiffline {bdf ids} {
6546 global diffids blobdifffd ctext curdiffstart
6547 global diffnexthead diffnextnote difffilestart
6548 global diffinhdr treediffs
6551 $ctext conf -state normal
6552 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
6553 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
6557 if {![string compare -length 11 "diff --git " $line]} {
6558 # trim off "diff --git "
6559 set line [string range $line 11 end]
6561 # start of a new file
6562 $ctext insert end "\n"
6563 set curdiffstart [$ctext index "end - 1c"]
6564 $ctext insert end "\n" filesep
6565 # If the name hasn't changed the length will be odd,
6566 # the middle char will be a space, and the two bits either
6567 # side will be a/name and b/name, or "a/name" and "b/name".
6568 # If the name has changed we'll get "rename from" and
6569 # "rename to" or "copy from" and "copy to" lines following this,
6570 # and we'll use them to get the filenames.
6571 # This complexity is necessary because spaces in the filename(s)
6572 # don't get escaped.
6573 set l [string length $line]
6574 set i [expr {$l / 2}]
6575 if {!(($l & 1) && [string index $line $i] eq " " &&
6576 [string range $line 2 [expr {$i - 1}]] eq \
6577 [string range $line [expr {$i + 3}] end])} {
6580 # unescape if quoted and chop off the a/ from the front
6581 if {[string index $line 0] eq "\""} {
6582 set fname [string range [lindex $line 0] 2 end]
6584 set fname [string range $line 2 [expr {$i - 1}]]
6586 makediffhdr $fname $ids
6588 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
6589 $line match f1l f1c f2l f2c rest]} {
6590 $ctext insert end "$line\n" hunksep
6593 } elseif {$diffinhdr} {
6594 if {![string compare -length 12 "rename from " $line]} {
6595 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
6596 if {[string index $fname 0] eq "\""} {
6597 set fname [lindex $fname 0]
6599 set i [lsearch -exact $treediffs($ids) $fname]
6601 setinlist difffilestart $i $curdiffstart
6603 } elseif {![string compare -length 10 $line "rename to "] ||
6604 ![string compare -length 8 $line "copy to "]} {
6605 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
6606 if {[string index $fname 0] eq "\""} {
6607 set fname [lindex $fname 0]
6609 makediffhdr $fname $ids
6610 } elseif {[string compare -length 3 $line "---"] == 0} {
6613 } elseif {[string compare -length 3 $line "+++"] == 0} {
6617 $ctext insert end "$line\n" filesep
6620 set x [string range $line 0 0]
6621 if {$x == "-" || $x == "+"} {
6622 set tag [expr {$x == "+"}]
6623 $ctext insert end "$line\n" d$tag
6624 } elseif {$x == " "} {
6625 $ctext insert end "$line\n"
6627 # "\ No newline at end of file",
6628 # or something else we don't recognize
6629 $ctext insert end "$line\n" hunksep
6633 $ctext conf -state disabled
6638 return [expr {$nr >= 1000? 2: 1}]
6641 proc changediffdisp {} {
6642 global ctext diffelide
6644 $ctext tag conf d0 -elide [lindex $diffelide 0]
6645 $ctext tag conf d1 -elide [lindex $diffelide 1]
6648 proc highlightfile {loc cline} {
6649 global ctext cflist cflist_top
6652 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
6653 $cflist tag add highlight $cline.0 "$cline.0 lineend"
6654 $cflist see $cline.0
6655 set cflist_top $cline
6659 global difffilestart ctext cmitmode
6661 if {$cmitmode eq "tree"} return
6664 set here [$ctext index @0,0]
6665 foreach loc $difffilestart {
6666 if {[$ctext compare $loc >= $here]} {
6667 highlightfile $prev $prevline
6673 highlightfile $prev $prevline
6677 global difffilestart ctext cmitmode
6679 if {$cmitmode eq "tree"} return
6680 set here [$ctext index @0,0]
6682 foreach loc $difffilestart {
6684 if {[$ctext compare $loc > $here]} {
6685 highlightfile $loc $line
6691 proc clear_ctext {{first 1.0}} {
6692 global ctext smarktop smarkbot
6695 set l [lindex [split $first .] 0]
6696 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
6699 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
6702 $ctext delete $first end
6703 if {$first eq "1.0"} {
6704 catch {unset pendinglinks}
6708 proc settabs {{firstab {}}} {
6709 global firsttabstop tabstop ctext have_tk85
6711 if {$firstab ne {} && $have_tk85} {
6712 set firsttabstop $firstab
6714 set w [font measure textfont "0"]
6715 if {$firsttabstop != 0} {
6716 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
6717 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
6718 } elseif {$have_tk85 || $tabstop != 8} {
6719 $ctext conf -tabs [expr {$tabstop * $w}]
6721 $ctext conf -tabs {}
6725 proc incrsearch {name ix op} {
6726 global ctext searchstring searchdirn
6728 $ctext tag remove found 1.0 end
6729 if {[catch {$ctext index anchor}]} {
6730 # no anchor set, use start of selection, or of visible area
6731 set sel [$ctext tag ranges sel]
6733 $ctext mark set anchor [lindex $sel 0]
6734 } elseif {$searchdirn eq "-forwards"} {
6735 $ctext mark set anchor @0,0
6737 $ctext mark set anchor @0,[winfo height $ctext]
6740 if {$searchstring ne {}} {
6741 set here [$ctext search $searchdirn -- $searchstring anchor]
6750 global sstring ctext searchstring searchdirn
6753 $sstring icursor end
6754 set searchdirn -forwards
6755 if {$searchstring ne {}} {
6756 set sel [$ctext tag ranges sel]
6758 set start "[lindex $sel 0] + 1c"
6759 } elseif {[catch {set start [$ctext index anchor]}]} {
6762 set match [$ctext search -count mlen -- $searchstring $start]
6763 $ctext tag remove sel 1.0 end
6769 set mend "$match + $mlen c"
6770 $ctext tag add sel $match $mend
6771 $ctext mark unset anchor
6775 proc dosearchback {} {
6776 global sstring ctext searchstring searchdirn
6779 $sstring icursor end
6780 set searchdirn -backwards
6781 if {$searchstring ne {}} {
6782 set sel [$ctext tag ranges sel]
6784 set start [lindex $sel 0]
6785 } elseif {[catch {set start [$ctext index anchor]}]} {
6786 set start @0,[winfo height $ctext]
6788 set match [$ctext search -backwards -count ml -- $searchstring $start]
6789 $ctext tag remove sel 1.0 end
6795 set mend "$match + $ml c"
6796 $ctext tag add sel $match $mend
6797 $ctext mark unset anchor
6801 proc searchmark {first last} {
6802 global ctext searchstring
6806 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
6807 if {$match eq {}} break
6808 set mend "$match + $mlen c"
6809 $ctext tag add found $match $mend
6813 proc searchmarkvisible {doall} {
6814 global ctext smarktop smarkbot
6816 set topline [lindex [split [$ctext index @0,0] .] 0]
6817 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
6818 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
6819 # no overlap with previous
6820 searchmark $topline $botline
6821 set smarktop $topline
6822 set smarkbot $botline
6824 if {$topline < $smarktop} {
6825 searchmark $topline [expr {$smarktop-1}]
6826 set smarktop $topline
6828 if {$botline > $smarkbot} {
6829 searchmark [expr {$smarkbot+1}] $botline
6830 set smarkbot $botline
6835 proc scrolltext {f0 f1} {
6838 .bleft.bottom.sb set $f0 $f1
6839 if {$searchstring ne {}} {
6845 global linespc charspc canvx0 canvy0
6846 global xspc1 xspc2 lthickness
6848 set linespc [font metrics mainfont -linespace]
6849 set charspc [font measure mainfont "m"]
6850 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
6851 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
6852 set lthickness [expr {int($linespc / 9) + 1}]
6853 set xspc1(0) $linespc
6861 set ymax [lindex [$canv cget -scrollregion] 3]
6862 if {$ymax eq {} || $ymax == 0} return
6863 set span [$canv yview]
6866 allcanvs yview moveto [lindex $span 0]
6868 if {[info exists selectedline]} {
6869 selectline $selectedline 0
6870 allcanvs yview moveto [lindex $span 0]
6874 proc parsefont {f n} {
6877 set fontattr($f,family) [lindex $n 0]
6879 if {$s eq {} || $s == 0} {
6882 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
6884 set fontattr($f,size) $s
6885 set fontattr($f,weight) normal
6886 set fontattr($f,slant) roman
6887 foreach style [lrange $n 2 end] {
6890 "bold" {set fontattr($f,weight) $style}
6892 "italic" {set fontattr($f,slant) $style}
6897 proc fontflags {f {isbold 0}} {
6900 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
6901 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
6902 -slant $fontattr($f,slant)]
6908 set n [list $fontattr($f,family) $fontattr($f,size)]
6909 if {$fontattr($f,weight) eq "bold"} {
6912 if {$fontattr($f,slant) eq "italic"} {
6918 proc incrfont {inc} {
6919 global mainfont textfont ctext canv cflist showrefstop
6920 global stopped entries fontattr
6923 set s $fontattr(mainfont,size)
6928 set fontattr(mainfont,size) $s
6929 font config mainfont -size $s
6930 font config mainfontbold -size $s
6931 set mainfont [fontname mainfont]
6932 set s $fontattr(textfont,size)
6937 set fontattr(textfont,size) $s
6938 font config textfont -size $s
6939 font config textfontbold -size $s
6940 set textfont [fontname textfont]
6947 global sha1entry sha1string
6948 if {[string length $sha1string] == 40} {
6949 $sha1entry delete 0 end
6953 proc sha1change {n1 n2 op} {
6954 global sha1string currentid sha1but
6955 if {$sha1string == {}
6956 || ([info exists currentid] && $sha1string == $currentid)} {
6961 if {[$sha1but cget -state] == $state} return
6962 if {$state == "normal"} {
6963 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
6965 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
6969 proc gotocommit {} {
6970 global sha1string tagids headids curview varcid
6972 if {$sha1string == {}
6973 || ([info exists currentid] && $sha1string == $currentid)} return
6974 if {[info exists tagids($sha1string)]} {
6975 set id $tagids($sha1string)
6976 } elseif {[info exists headids($sha1string)]} {
6977 set id $headids($sha1string)
6979 set id [string tolower $sha1string]
6980 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
6981 set matches [array names varcid "$curview,$id*"]
6982 if {$matches ne {}} {
6983 if {[llength $matches] > 1} {
6984 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
6987 set id [lindex [split [lindex $matches 0] ","] 1]
6991 if {[commitinview $id $curview]} {
6992 selectline [rowofcommit $id] 1
6995 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
6996 set msg [mc "SHA1 id %s is not known" $sha1string]
6998 set msg [mc "Tag/Head %s is not known" $sha1string]
7003 proc lineenter {x y id} {
7004 global hoverx hovery hoverid hovertimer
7005 global commitinfo canv
7007 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7011 if {[info exists hovertimer]} {
7012 after cancel $hovertimer
7014 set hovertimer [after 500 linehover]
7018 proc linemotion {x y id} {
7019 global hoverx hovery hoverid hovertimer
7021 if {[info exists hoverid] && $id == $hoverid} {
7024 if {[info exists hovertimer]} {
7025 after cancel $hovertimer
7027 set hovertimer [after 500 linehover]
7031 proc lineleave {id} {
7032 global hoverid hovertimer canv
7034 if {[info exists hoverid] && $id == $hoverid} {
7036 if {[info exists hovertimer]} {
7037 after cancel $hovertimer
7045 global hoverx hovery hoverid hovertimer
7046 global canv linespc lthickness
7049 set text [lindex $commitinfo($hoverid) 0]
7050 set ymax [lindex [$canv cget -scrollregion] 3]
7051 if {$ymax == {}} return
7052 set yfrac [lindex [$canv yview] 0]
7053 set x [expr {$hoverx + 2 * $linespc}]
7054 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7055 set x0 [expr {$x - 2 * $lthickness}]
7056 set y0 [expr {$y - 2 * $lthickness}]
7057 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7058 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7059 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7060 -fill \#ffff80 -outline black -width 1 -tags hover]
7062 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7067 proc clickisonarrow {id y} {
7070 set ranges [rowranges $id]
7071 set thresh [expr {2 * $lthickness + 6}]
7072 set n [expr {[llength $ranges] - 1}]
7073 for {set i 1} {$i < $n} {incr i} {
7074 set row [lindex $ranges $i]
7075 if {abs([yc $row] - $y) < $thresh} {
7082 proc arrowjump {id n y} {
7085 # 1 <-> 2, 3 <-> 4, etc...
7086 set n [expr {(($n - 1) ^ 1) + 1}]
7087 set row [lindex [rowranges $id] $n]
7089 set ymax [lindex [$canv cget -scrollregion] 3]
7090 if {$ymax eq {} || $ymax <= 0} return
7091 set view [$canv yview]
7092 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7093 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7097 allcanvs yview moveto $yfrac
7100 proc lineclick {x y id isnew} {
7101 global ctext commitinfo children canv thickerline curview
7103 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7108 # draw this line thicker than normal
7112 set ymax [lindex [$canv cget -scrollregion] 3]
7113 if {$ymax eq {}} return
7114 set yfrac [lindex [$canv yview] 0]
7115 set y [expr {$y + $yfrac * $ymax}]
7117 set dirn [clickisonarrow $id $y]
7119 arrowjump $id $dirn $y
7124 addtohistory [list lineclick $x $y $id 0]
7126 # fill the details pane with info about this line
7127 $ctext conf -state normal
7130 $ctext insert end "[mc "Parent"]:\t"
7131 $ctext insert end $id link0
7133 set info $commitinfo($id)
7134 $ctext insert end "\n\t[lindex $info 0]\n"
7135 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7136 set date [formatdate [lindex $info 2]]
7137 $ctext insert end "\t[mc "Date"]:\t$date\n"
7138 set kids $children($curview,$id)
7140 $ctext insert end "\n[mc "Children"]:"
7142 foreach child $kids {
7144 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7145 set info $commitinfo($child)
7146 $ctext insert end "\n\t"
7147 $ctext insert end $child link$i
7148 setlink $child link$i
7149 $ctext insert end "\n\t[lindex $info 0]"
7150 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7151 set date [formatdate [lindex $info 2]]
7152 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7155 $ctext conf -state disabled
7159 proc normalline {} {
7161 if {[info exists thickerline]} {
7170 if {[commitinview $id $curview]} {
7171 selectline [rowofcommit $id] 1
7177 if {![info exists startmstime]} {
7178 set startmstime [clock clicks -milliseconds]
7180 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7183 proc rowmenu {x y id} {
7184 global rowctxmenu selectedline rowmenuid curview
7185 global nullid nullid2 fakerowmenu mainhead
7189 if {![info exists selectedline]
7190 || [rowofcommit $id] eq $selectedline} {
7195 if {$id ne $nullid && $id ne $nullid2} {
7196 set menu $rowctxmenu
7197 if {$mainhead ne {}} {
7198 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7200 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7203 set menu $fakerowmenu
7205 $menu entryconfigure [mc "Diff this -> selected"] -state $state
7206 $menu entryconfigure [mc "Diff selected -> this"] -state $state
7207 $menu entryconfigure [mc "Make patch"] -state $state
7208 tk_popup $menu $x $y
7211 proc diffvssel {dirn} {
7212 global rowmenuid selectedline
7214 if {![info exists selectedline]} return
7216 set oldid [commitonrow $selectedline]
7217 set newid $rowmenuid
7219 set oldid $rowmenuid
7220 set newid [commitonrow $selectedline]
7222 addtohistory [list doseldiff $oldid $newid]
7223 doseldiff $oldid $newid
7226 proc doseldiff {oldid newid} {
7230 $ctext conf -state normal
7232 init_flist [mc "Top"]
7233 $ctext insert end "[mc "From"] "
7234 $ctext insert end $oldid link0
7235 setlink $oldid link0
7236 $ctext insert end "\n "
7237 $ctext insert end [lindex $commitinfo($oldid) 0]
7238 $ctext insert end "\n\n[mc "To"] "
7239 $ctext insert end $newid link1
7240 setlink $newid link1
7241 $ctext insert end "\n "
7242 $ctext insert end [lindex $commitinfo($newid) 0]
7243 $ctext insert end "\n"
7244 $ctext conf -state disabled
7245 $ctext tag remove found 1.0 end
7246 startdiff [list $oldid $newid]
7250 global rowmenuid currentid commitinfo patchtop patchnum
7252 if {![info exists currentid]} return
7253 set oldid $currentid
7254 set oldhead [lindex $commitinfo($oldid) 0]
7255 set newid $rowmenuid
7256 set newhead [lindex $commitinfo($newid) 0]
7259 catch {destroy $top}
7261 label $top.title -text [mc "Generate patch"]
7262 grid $top.title - -pady 10
7263 label $top.from -text [mc "From:"]
7264 entry $top.fromsha1 -width 40 -relief flat
7265 $top.fromsha1 insert 0 $oldid
7266 $top.fromsha1 conf -state readonly
7267 grid $top.from $top.fromsha1 -sticky w
7268 entry $top.fromhead -width 60 -relief flat
7269 $top.fromhead insert 0 $oldhead
7270 $top.fromhead conf -state readonly
7271 grid x $top.fromhead -sticky w
7272 label $top.to -text [mc "To:"]
7273 entry $top.tosha1 -width 40 -relief flat
7274 $top.tosha1 insert 0 $newid
7275 $top.tosha1 conf -state readonly
7276 grid $top.to $top.tosha1 -sticky w
7277 entry $top.tohead -width 60 -relief flat
7278 $top.tohead insert 0 $newhead
7279 $top.tohead conf -state readonly
7280 grid x $top.tohead -sticky w
7281 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7282 grid $top.rev x -pady 10
7283 label $top.flab -text [mc "Output file:"]
7284 entry $top.fname -width 60
7285 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7287 grid $top.flab $top.fname -sticky w
7289 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7290 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7291 grid $top.buts.gen $top.buts.can
7292 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7293 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7294 grid $top.buts - -pady 10 -sticky ew
7298 proc mkpatchrev {} {
7301 set oldid [$patchtop.fromsha1 get]
7302 set oldhead [$patchtop.fromhead get]
7303 set newid [$patchtop.tosha1 get]
7304 set newhead [$patchtop.tohead get]
7305 foreach e [list fromsha1 fromhead tosha1 tohead] \
7306 v [list $newid $newhead $oldid $oldhead] {
7307 $patchtop.$e conf -state normal
7308 $patchtop.$e delete 0 end
7309 $patchtop.$e insert 0 $v
7310 $patchtop.$e conf -state readonly
7315 global patchtop nullid nullid2
7317 set oldid [$patchtop.fromsha1 get]
7318 set newid [$patchtop.tosha1 get]
7319 set fname [$patchtop.fname get]
7320 set cmd [diffcmd [list $oldid $newid] -p]
7321 # trim off the initial "|"
7322 set cmd [lrange $cmd 1 end]
7323 lappend cmd >$fname &
7324 if {[catch {eval exec $cmd} err]} {
7325 error_popup "[mc "Error creating patch:"] $err"
7327 catch {destroy $patchtop}
7331 proc mkpatchcan {} {
7334 catch {destroy $patchtop}
7339 global rowmenuid mktagtop commitinfo
7343 catch {destroy $top}
7345 label $top.title -text [mc "Create tag"]
7346 grid $top.title - -pady 10
7347 label $top.id -text [mc "ID:"]
7348 entry $top.sha1 -width 40 -relief flat
7349 $top.sha1 insert 0 $rowmenuid
7350 $top.sha1 conf -state readonly
7351 grid $top.id $top.sha1 -sticky w
7352 entry $top.head -width 60 -relief flat
7353 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7354 $top.head conf -state readonly
7355 grid x $top.head -sticky w
7356 label $top.tlab -text [mc "Tag name:"]
7357 entry $top.tag -width 60
7358 grid $top.tlab $top.tag -sticky w
7360 button $top.buts.gen -text [mc "Create"] -command mktaggo
7361 button $top.buts.can -text [mc "Cancel"] -command mktagcan
7362 grid $top.buts.gen $top.buts.can
7363 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7364 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7365 grid $top.buts - -pady 10 -sticky ew
7370 global mktagtop env tagids idtags
7372 set id [$mktagtop.sha1 get]
7373 set tag [$mktagtop.tag get]
7375 error_popup [mc "No tag name specified"]
7378 if {[info exists tagids($tag)]} {
7379 error_popup [mc "Tag \"%s\" already exists" $tag]
7383 exec git tag $tag $id
7385 error_popup "[mc "Error creating tag:"] $err"
7389 set tagids($tag) $id
7390 lappend idtags($id) $tag
7397 proc redrawtags {id} {
7398 global canv linehtag idpos currentid curview
7399 global canvxmax iddrawn
7401 if {![commitinview $id $curview]} return
7402 if {![info exists iddrawn($id)]} return
7403 set row [rowofcommit $id]
7404 $canv delete tag.$id
7405 set xt [eval drawtags $id $idpos($id)]
7406 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
7407 set text [$canv itemcget $linehtag($row) -text]
7408 set font [$canv itemcget $linehtag($row) -font]
7409 set xr [expr {$xt + [font measure $font $text]}]
7410 if {$xr > $canvxmax} {
7414 if {[info exists currentid] && $currentid == $id} {
7422 catch {destroy $mktagtop}
7431 proc writecommit {} {
7432 global rowmenuid wrcomtop commitinfo wrcomcmd
7434 set top .writecommit
7436 catch {destroy $top}
7438 label $top.title -text [mc "Write commit to file"]
7439 grid $top.title - -pady 10
7440 label $top.id -text [mc "ID:"]
7441 entry $top.sha1 -width 40 -relief flat
7442 $top.sha1 insert 0 $rowmenuid
7443 $top.sha1 conf -state readonly
7444 grid $top.id $top.sha1 -sticky w
7445 entry $top.head -width 60 -relief flat
7446 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7447 $top.head conf -state readonly
7448 grid x $top.head -sticky w
7449 label $top.clab -text [mc "Command:"]
7450 entry $top.cmd -width 60 -textvariable wrcomcmd
7451 grid $top.clab $top.cmd -sticky w -pady 10
7452 label $top.flab -text [mc "Output file:"]
7453 entry $top.fname -width 60
7454 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
7455 grid $top.flab $top.fname -sticky w
7457 button $top.buts.gen -text [mc "Write"] -command wrcomgo
7458 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
7459 grid $top.buts.gen $top.buts.can
7460 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7461 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7462 grid $top.buts - -pady 10 -sticky ew
7469 set id [$wrcomtop.sha1 get]
7470 set cmd "echo $id | [$wrcomtop.cmd get]"
7471 set fname [$wrcomtop.fname get]
7472 if {[catch {exec sh -c $cmd >$fname &} err]} {
7473 error_popup "[mc "Error writing commit:"] $err"
7475 catch {destroy $wrcomtop}
7482 catch {destroy $wrcomtop}
7487 global rowmenuid mkbrtop
7490 catch {destroy $top}
7492 label $top.title -text [mc "Create new branch"]
7493 grid $top.title - -pady 10
7494 label $top.id -text [mc "ID:"]
7495 entry $top.sha1 -width 40 -relief flat
7496 $top.sha1 insert 0 $rowmenuid
7497 $top.sha1 conf -state readonly
7498 grid $top.id $top.sha1 -sticky w
7499 label $top.nlab -text [mc "Name:"]
7500 entry $top.name -width 40
7501 grid $top.nlab $top.name -sticky w
7503 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
7504 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
7505 grid $top.buts.go $top.buts.can
7506 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7507 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7508 grid $top.buts - -pady 10 -sticky ew
7513 global headids idheads
7515 set name [$top.name get]
7516 set id [$top.sha1 get]
7518 error_popup [mc "Please specify a name for the new branch"]
7521 catch {destroy $top}
7525 exec git branch $name $id
7530 set headids($name) $id
7531 lappend idheads($id) $name
7540 proc cherrypick {} {
7541 global rowmenuid curview
7542 global mainhead mainheadid
7544 set oldhead [exec git rev-parse HEAD]
7545 set dheads [descheads $rowmenuid]
7546 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
7547 set ok [confirm_popup [mc "Commit %s is already\
7548 included in branch %s -- really re-apply it?" \
7549 [string range $rowmenuid 0 7] $mainhead]]
7552 nowbusy cherrypick [mc "Cherry-picking"]
7554 # Unfortunately git-cherry-pick writes stuff to stderr even when
7555 # no error occurs, and exec takes that as an indication of error...
7556 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
7561 set newhead [exec git rev-parse HEAD]
7562 if {$newhead eq $oldhead} {
7564 error_popup [mc "No changes committed"]
7567 addnewchild $newhead $oldhead
7568 if {[commitinview $oldhead $curview]} {
7569 insertrow $newhead $oldhead $curview
7570 if {$mainhead ne {}} {
7571 movehead $newhead $mainhead
7572 movedhead $newhead $mainhead
7573 set mainheadid $newhead
7583 global mainhead rowmenuid confirm_ok resettype
7586 set w ".confirmreset"
7589 wm title $w [mc "Confirm reset"]
7590 message $w.m -text \
7591 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
7592 -justify center -aspect 1000
7593 pack $w.m -side top -fill x -padx 20 -pady 20
7594 frame $w.f -relief sunken -border 2
7595 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
7596 grid $w.f.rt -sticky w
7598 radiobutton $w.f.soft -value soft -variable resettype -justify left \
7599 -text [mc "Soft: Leave working tree and index untouched"]
7600 grid $w.f.soft -sticky w
7601 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
7602 -text [mc "Mixed: Leave working tree untouched, reset index"]
7603 grid $w.f.mixed -sticky w
7604 radiobutton $w.f.hard -value hard -variable resettype -justify left \
7605 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
7606 grid $w.f.hard -sticky w
7607 pack $w.f -side top -fill x
7608 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
7609 pack $w.ok -side left -fill x -padx 20 -pady 20
7610 button $w.cancel -text [mc Cancel] -command "destroy $w"
7611 pack $w.cancel -side right -fill x -padx 20 -pady 20
7612 bind $w <Visibility> "grab $w; focus $w"
7614 if {!$confirm_ok} return
7615 if {[catch {set fd [open \
7616 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
7620 filerun $fd [list readresetstat $fd]
7621 nowbusy reset [mc "Resetting"]
7626 proc readresetstat {fd} {
7627 global mainhead mainheadid showlocalchanges rprogcoord
7629 if {[gets $fd line] >= 0} {
7630 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7631 set rprogcoord [expr {1.0 * $m / $n}]
7639 if {[catch {close $fd} err]} {
7642 set oldhead $mainheadid
7643 set newhead [exec git rev-parse HEAD]
7644 if {$newhead ne $oldhead} {
7645 movehead $newhead $mainhead
7646 movedhead $newhead $mainhead
7647 set mainheadid $newhead
7651 if {$showlocalchanges} {
7657 # context menu for a head
7658 proc headmenu {x y id head} {
7659 global headmenuid headmenuhead headctxmenu mainhead
7663 set headmenuhead $head
7665 if {$head eq $mainhead} {
7668 $headctxmenu entryconfigure 0 -state $state
7669 $headctxmenu entryconfigure 1 -state $state
7670 tk_popup $headctxmenu $x $y
7674 global headmenuid headmenuhead mainhead headids
7675 global showlocalchanges mainheadid
7677 # check the tree is clean first??
7678 nowbusy checkout [mc "Checking out"]
7682 set fd [open [list | git checkout $headmenuhead 2>@1] r]
7686 if {$showlocalchanges} {
7690 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
7694 proc readcheckoutstat {fd newhead newheadid} {
7695 global mainhead mainheadid headids showlocalchanges progresscoords
7697 if {[gets $fd line] >= 0} {
7698 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7699 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
7704 set progresscoords {0 0}
7707 if {[catch {close $fd} err]} {
7710 set oldmainhead $mainhead
7711 set mainhead $newhead
7712 set mainheadid $newheadid
7713 if {[info exists headids($oldmainhead)]} {
7714 redrawtags $headids($oldmainhead)
7716 redrawtags $newheadid
7718 if {$showlocalchanges} {
7724 global headmenuid headmenuhead mainhead
7727 set head $headmenuhead
7729 # this check shouldn't be needed any more...
7730 if {$head eq $mainhead} {
7731 error_popup [mc "Cannot delete the currently checked-out branch"]
7734 set dheads [descheads $id]
7735 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
7736 # the stuff on this branch isn't on any other branch
7737 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
7738 branch.\nReally delete branch %s?" $head $head]]} return
7742 if {[catch {exec git branch -D $head} err]} {
7747 removehead $id $head
7748 removedhead $id $head
7755 # Display a list of tags and heads
7757 global showrefstop bgcolor fgcolor selectbgcolor
7758 global bglist fglist reflistfilter reflist maincursor
7761 set showrefstop $top
7762 if {[winfo exists $top]} {
7768 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
7769 text $top.list -background $bgcolor -foreground $fgcolor \
7770 -selectbackground $selectbgcolor -font mainfont \
7771 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
7772 -width 30 -height 20 -cursor $maincursor \
7773 -spacing1 1 -spacing3 1 -state disabled
7774 $top.list tag configure highlight -background $selectbgcolor
7775 lappend bglist $top.list
7776 lappend fglist $top.list
7777 scrollbar $top.ysb -command "$top.list yview" -orient vertical
7778 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
7779 grid $top.list $top.ysb -sticky nsew
7780 grid $top.xsb x -sticky ew
7782 label $top.f.l -text "[mc "Filter"]: "
7783 entry $top.f.e -width 20 -textvariable reflistfilter
7784 set reflistfilter "*"
7785 trace add variable reflistfilter write reflistfilter_change
7786 pack $top.f.e -side right -fill x -expand 1
7787 pack $top.f.l -side left
7788 grid $top.f - -sticky ew -pady 2
7789 button $top.close -command [list destroy $top] -text [mc "Close"]
7791 grid columnconfigure $top 0 -weight 1
7792 grid rowconfigure $top 0 -weight 1
7793 bind $top.list <1> {break}
7794 bind $top.list <B1-Motion> {break}
7795 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
7800 proc sel_reflist {w x y} {
7801 global showrefstop reflist headids tagids otherrefids
7803 if {![winfo exists $showrefstop]} return
7804 set l [lindex [split [$w index "@$x,$y"] "."] 0]
7805 set ref [lindex $reflist [expr {$l-1}]]
7806 set n [lindex $ref 0]
7807 switch -- [lindex $ref 1] {
7808 "H" {selbyid $headids($n)}
7809 "T" {selbyid $tagids($n)}
7810 "o" {selbyid $otherrefids($n)}
7812 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
7815 proc unsel_reflist {} {
7818 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7819 $showrefstop.list tag remove highlight 0.0 end
7822 proc reflistfilter_change {n1 n2 op} {
7823 global reflistfilter
7825 after cancel refill_reflist
7826 after 200 refill_reflist
7829 proc refill_reflist {} {
7830 global reflist reflistfilter showrefstop headids tagids otherrefids
7831 global curview commitinterest
7833 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7835 foreach n [array names headids] {
7836 if {[string match $reflistfilter $n]} {
7837 if {[commitinview $headids($n) $curview]} {
7838 lappend refs [list $n H]
7840 set commitinterest($headids($n)) {run refill_reflist}
7844 foreach n [array names tagids] {
7845 if {[string match $reflistfilter $n]} {
7846 if {[commitinview $tagids($n) $curview]} {
7847 lappend refs [list $n T]
7849 set commitinterest($tagids($n)) {run refill_reflist}
7853 foreach n [array names otherrefids] {
7854 if {[string match $reflistfilter $n]} {
7855 if {[commitinview $otherrefids($n) $curview]} {
7856 lappend refs [list $n o]
7858 set commitinterest($otherrefids($n)) {run refill_reflist}
7862 set refs [lsort -index 0 $refs]
7863 if {$refs eq $reflist} return
7865 # Update the contents of $showrefstop.list according to the
7866 # differences between $reflist (old) and $refs (new)
7867 $showrefstop.list conf -state normal
7868 $showrefstop.list insert end "\n"
7871 while {$i < [llength $reflist] || $j < [llength $refs]} {
7872 if {$i < [llength $reflist]} {
7873 if {$j < [llength $refs]} {
7874 set cmp [string compare [lindex $reflist $i 0] \
7875 [lindex $refs $j 0]]
7877 set cmp [string compare [lindex $reflist $i 1] \
7878 [lindex $refs $j 1]]
7888 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
7896 set l [expr {$j + 1}]
7897 $showrefstop.list image create $l.0 -align baseline \
7898 -image reficon-[lindex $refs $j 1] -padx 2
7899 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
7905 # delete last newline
7906 $showrefstop.list delete end-2c end-1c
7907 $showrefstop.list conf -state disabled
7910 # Stuff for finding nearby tags
7911 proc getallcommits {} {
7912 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
7913 global idheads idtags idotherrefs allparents tagobjid
7915 if {![info exists allcommits]} {
7921 set allccache [file join [gitdir] "gitk.cache"]
7923 set f [open $allccache r]
7932 set cmd [list | git rev-list --parents]
7933 set allcupdate [expr {$seeds ne {}}]
7937 set refs [concat [array names idheads] [array names idtags] \
7938 [array names idotherrefs]]
7941 foreach name [array names tagobjid] {
7942 lappend tagobjs $tagobjid($name)
7944 foreach id [lsort -unique $refs] {
7945 if {![info exists allparents($id)] &&
7946 [lsearch -exact $tagobjs $id] < 0} {
7957 set fd [open [concat $cmd $ids] r]
7958 fconfigure $fd -blocking 0
7961 filerun $fd [list getallclines $fd]
7967 # Since most commits have 1 parent and 1 child, we group strings of
7968 # such commits into "arcs" joining branch/merge points (BMPs), which
7969 # are commits that either don't have 1 parent or don't have 1 child.
7971 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
7972 # arcout(id) - outgoing arcs for BMP
7973 # arcids(a) - list of IDs on arc including end but not start
7974 # arcstart(a) - BMP ID at start of arc
7975 # arcend(a) - BMP ID at end of arc
7976 # growing(a) - arc a is still growing
7977 # arctags(a) - IDs out of arcids (excluding end) that have tags
7978 # archeads(a) - IDs out of arcids (excluding end) that have heads
7979 # The start of an arc is at the descendent end, so "incoming" means
7980 # coming from descendents, and "outgoing" means going towards ancestors.
7982 proc getallclines {fd} {
7983 global allparents allchildren idtags idheads nextarc
7984 global arcnos arcids arctags arcout arcend arcstart archeads growing
7985 global seeds allcommits cachedarcs allcupdate
7988 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
7989 set id [lindex $line 0]
7990 if {[info exists allparents($id)]} {
7995 set olds [lrange $line 1 end]
7996 set allparents($id) $olds
7997 if {![info exists allchildren($id)]} {
7998 set allchildren($id) {}
8003 if {[llength $olds] == 1 && [llength $a] == 1} {
8004 lappend arcids($a) $id
8005 if {[info exists idtags($id)]} {
8006 lappend arctags($a) $id
8008 if {[info exists idheads($id)]} {
8009 lappend archeads($a) $id
8011 if {[info exists allparents($olds)]} {
8012 # seen parent already
8013 if {![info exists arcout($olds)]} {
8016 lappend arcids($a) $olds
8017 set arcend($a) $olds
8020 lappend allchildren($olds) $id
8021 lappend arcnos($olds) $a
8025 foreach a $arcnos($id) {
8026 lappend arcids($a) $id
8033 lappend allchildren($p) $id
8034 set a [incr nextarc]
8035 set arcstart($a) $id
8042 if {[info exists allparents($p)]} {
8043 # seen it already, may need to make a new branch
8044 if {![info exists arcout($p)]} {
8047 lappend arcids($a) $p
8051 lappend arcnos($p) $a
8056 global cached_dheads cached_dtags cached_atags
8057 catch {unset cached_dheads}
8058 catch {unset cached_dtags}
8059 catch {unset cached_atags}
8062 return [expr {$nid >= 1000? 2: 1}]
8066 fconfigure $fd -blocking 1
8069 # got an error reading the list of commits
8070 # if we were updating, try rereading the whole thing again
8076 error_popup "[mc "Error reading commit topology information;\
8077 branch and preceding/following tag information\
8078 will be incomplete."]\n($err)"
8081 if {[incr allcommits -1] == 0} {
8091 proc recalcarc {a} {
8092 global arctags archeads arcids idtags idheads
8096 foreach id [lrange $arcids($a) 0 end-1] {
8097 if {[info exists idtags($id)]} {
8100 if {[info exists idheads($id)]} {
8105 set archeads($a) $ah
8109 global arcnos arcids nextarc arctags archeads idtags idheads
8110 global arcstart arcend arcout allparents growing
8113 if {[llength $a] != 1} {
8114 puts "oops splitarc called but [llength $a] arcs already"
8118 set i [lsearch -exact $arcids($a) $p]
8120 puts "oops splitarc $p not in arc $a"
8123 set na [incr nextarc]
8124 if {[info exists arcend($a)]} {
8125 set arcend($na) $arcend($a)
8127 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8128 set j [lsearch -exact $arcnos($l) $a]
8129 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8131 set tail [lrange $arcids($a) [expr {$i+1}] end]
8132 set arcids($a) [lrange $arcids($a) 0 $i]
8134 set arcstart($na) $p
8136 set arcids($na) $tail
8137 if {[info exists growing($a)]} {
8143 if {[llength $arcnos($id)] == 1} {
8146 set j [lsearch -exact $arcnos($id) $a]
8147 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8151 # reconstruct tags and heads lists
8152 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8157 set archeads($na) {}
8161 # Update things for a new commit added that is a child of one
8162 # existing commit. Used when cherry-picking.
8163 proc addnewchild {id p} {
8164 global allparents allchildren idtags nextarc
8165 global arcnos arcids arctags arcout arcend arcstart archeads growing
8166 global seeds allcommits
8168 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8169 set allparents($id) [list $p]
8170 set allchildren($id) {}
8173 lappend allchildren($p) $id
8174 set a [incr nextarc]
8175 set arcstart($a) $id
8178 set arcids($a) [list $p]
8180 if {![info exists arcout($p)]} {
8183 lappend arcnos($p) $a
8184 set arcout($id) [list $a]
8187 # This implements a cache for the topology information.
8188 # The cache saves, for each arc, the start and end of the arc,
8189 # the ids on the arc, and the outgoing arcs from the end.
8190 proc readcache {f} {
8191 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8192 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8197 if {$lim - $a > 500} {
8198 set lim [expr {$a + 500}]
8202 # finish reading the cache and setting up arctags, etc.
8204 if {$line ne "1"} {error "bad final version"}
8206 foreach id [array names idtags] {
8207 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8208 [llength $allparents($id)] == 1} {
8209 set a [lindex $arcnos($id) 0]
8210 if {$arctags($a) eq {}} {
8215 foreach id [array names idheads] {
8216 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8217 [llength $allparents($id)] == 1} {
8218 set a [lindex $arcnos($id) 0]
8219 if {$archeads($a) eq {}} {
8224 foreach id [lsort -unique $possible_seeds] {
8225 if {$arcnos($id) eq {}} {
8231 while {[incr a] <= $lim} {
8233 if {[llength $line] != 3} {error "bad line"}
8234 set s [lindex $line 0]
8236 lappend arcout($s) $a
8237 if {![info exists arcnos($s)]} {
8238 lappend possible_seeds $s
8241 set e [lindex $line 1]
8246 if {![info exists arcout($e)]} {
8250 set arcids($a) [lindex $line 2]
8251 foreach id $arcids($a) {
8252 lappend allparents($s) $id
8254 lappend arcnos($id) $a
8256 if {![info exists allparents($s)]} {
8257 set allparents($s) {}
8262 set nextarc [expr {$a - 1}]
8275 global nextarc cachedarcs possible_seeds
8279 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
8280 # make sure it's an integer
8281 set cachedarcs [expr {int([lindex $line 1])}]
8282 if {$cachedarcs < 0} {error "bad number of arcs"}
8284 set possible_seeds {}
8292 proc dropcache {err} {
8293 global allcwait nextarc cachedarcs seeds
8295 #puts "dropping cache ($err)"
8296 foreach v {arcnos arcout arcids arcstart arcend growing \
8297 arctags archeads allparents allchildren} {
8308 proc writecache {f} {
8309 global cachearc cachedarcs allccache
8310 global arcstart arcend arcnos arcids arcout
8314 if {$lim - $a > 1000} {
8315 set lim [expr {$a + 1000}]
8318 while {[incr a] <= $lim} {
8319 if {[info exists arcend($a)]} {
8320 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
8322 puts $f [list $arcstart($a) {} $arcids($a)]
8327 catch {file delete $allccache}
8328 #puts "writing cache failed ($err)"
8331 set cachearc [expr {$a - 1}]
8332 if {$a > $cachedarcs} {
8341 global nextarc cachedarcs cachearc allccache
8343 if {$nextarc == $cachedarcs} return
8345 set cachedarcs $nextarc
8347 set f [open $allccache w]
8348 puts $f [list 1 $cachedarcs]
8353 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
8354 # or 0 if neither is true.
8355 proc anc_or_desc {a b} {
8356 global arcout arcstart arcend arcnos cached_isanc
8358 if {$arcnos($a) eq $arcnos($b)} {
8359 # Both are on the same arc(s); either both are the same BMP,
8360 # or if one is not a BMP, the other is also not a BMP or is
8361 # the BMP at end of the arc (and it only has 1 incoming arc).
8362 # Or both can be BMPs with no incoming arcs.
8363 if {$a eq $b || $arcnos($a) eq {}} {
8366 # assert {[llength $arcnos($a)] == 1}
8367 set arc [lindex $arcnos($a) 0]
8368 set i [lsearch -exact $arcids($arc) $a]
8369 set j [lsearch -exact $arcids($arc) $b]
8370 if {$i < 0 || $i > $j} {
8377 if {![info exists arcout($a)]} {
8378 set arc [lindex $arcnos($a) 0]
8379 if {[info exists arcend($arc)]} {
8380 set aend $arcend($arc)
8384 set a $arcstart($arc)
8388 if {![info exists arcout($b)]} {
8389 set arc [lindex $arcnos($b) 0]
8390 if {[info exists arcend($arc)]} {
8391 set bend $arcend($arc)
8395 set b $arcstart($arc)
8405 if {[info exists cached_isanc($a,$bend)]} {
8406 if {$cached_isanc($a,$bend)} {
8410 if {[info exists cached_isanc($b,$aend)]} {
8411 if {$cached_isanc($b,$aend)} {
8414 if {[info exists cached_isanc($a,$bend)]} {
8419 set todo [list $a $b]
8422 for {set i 0} {$i < [llength $todo]} {incr i} {
8423 set x [lindex $todo $i]
8424 if {$anc($x) eq {}} {
8427 foreach arc $arcnos($x) {
8428 set xd $arcstart($arc)
8430 set cached_isanc($a,$bend) 1
8431 set cached_isanc($b,$aend) 0
8433 } elseif {$xd eq $aend} {
8434 set cached_isanc($b,$aend) 1
8435 set cached_isanc($a,$bend) 0
8438 if {![info exists anc($xd)]} {
8439 set anc($xd) $anc($x)
8441 } elseif {$anc($xd) ne $anc($x)} {
8446 set cached_isanc($a,$bend) 0
8447 set cached_isanc($b,$aend) 0
8451 # This identifies whether $desc has an ancestor that is
8452 # a growing tip of the graph and which is not an ancestor of $anc
8453 # and returns 0 if so and 1 if not.
8454 # If we subsequently discover a tag on such a growing tip, and that
8455 # turns out to be a descendent of $anc (which it could, since we
8456 # don't necessarily see children before parents), then $desc
8457 # isn't a good choice to display as a descendent tag of
8458 # $anc (since it is the descendent of another tag which is
8459 # a descendent of $anc). Similarly, $anc isn't a good choice to
8460 # display as a ancestor tag of $desc.
8462 proc is_certain {desc anc} {
8463 global arcnos arcout arcstart arcend growing problems
8466 if {[llength $arcnos($anc)] == 1} {
8467 # tags on the same arc are certain
8468 if {$arcnos($desc) eq $arcnos($anc)} {
8471 if {![info exists arcout($anc)]} {
8472 # if $anc is partway along an arc, use the start of the arc instead
8473 set a [lindex $arcnos($anc) 0]
8474 set anc $arcstart($a)
8477 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
8480 set a [lindex $arcnos($desc) 0]
8486 set anclist [list $x]
8490 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
8491 set x [lindex $anclist $i]
8496 foreach a $arcout($x) {
8497 if {[info exists growing($a)]} {
8498 if {![info exists growanc($x)] && $dl($x)} {
8504 if {[info exists dl($y)]} {
8508 if {![info exists done($y)]} {
8511 if {[info exists growanc($x)]} {
8515 for {set k 0} {$k < [llength $xl]} {incr k} {
8516 set z [lindex $xl $k]
8517 foreach c $arcout($z) {
8518 if {[info exists arcend($c)]} {
8520 if {[info exists dl($v)] && $dl($v)} {
8522 if {![info exists done($v)]} {
8525 if {[info exists growanc($v)]} {
8535 } elseif {$y eq $anc || !$dl($x)} {
8546 foreach x [array names growanc] {
8555 proc validate_arctags {a} {
8556 global arctags idtags
8560 foreach id $arctags($a) {
8562 if {![info exists idtags($id)]} {
8563 set na [lreplace $na $i $i]
8570 proc validate_archeads {a} {
8571 global archeads idheads
8574 set na $archeads($a)
8575 foreach id $archeads($a) {
8577 if {![info exists idheads($id)]} {
8578 set na [lreplace $na $i $i]
8582 set archeads($a) $na
8585 # Return the list of IDs that have tags that are descendents of id,
8586 # ignoring IDs that are descendents of IDs already reported.
8587 proc desctags {id} {
8588 global arcnos arcstart arcids arctags idtags allparents
8589 global growing cached_dtags
8591 if {![info exists allparents($id)]} {
8594 set t1 [clock clicks -milliseconds]
8596 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8597 # part-way along an arc; check that arc first
8598 set a [lindex $arcnos($id) 0]
8599 if {$arctags($a) ne {}} {
8601 set i [lsearch -exact $arcids($a) $id]
8603 foreach t $arctags($a) {
8604 set j [lsearch -exact $arcids($a) $t]
8612 set id $arcstart($a)
8613 if {[info exists idtags($id)]} {
8617 if {[info exists cached_dtags($id)]} {
8618 return $cached_dtags($id)
8625 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8626 set id [lindex $todo $i]
8628 set ta [info exists hastaggedancestor($id)]
8632 # ignore tags on starting node
8633 if {!$ta && $i > 0} {
8634 if {[info exists idtags($id)]} {
8637 } elseif {[info exists cached_dtags($id)]} {
8638 set tagloc($id) $cached_dtags($id)
8642 foreach a $arcnos($id) {
8644 if {!$ta && $arctags($a) ne {}} {
8646 if {$arctags($a) ne {}} {
8647 lappend tagloc($id) [lindex $arctags($a) end]
8650 if {$ta || $arctags($a) ne {}} {
8651 set tomark [list $d]
8652 for {set j 0} {$j < [llength $tomark]} {incr j} {
8653 set dd [lindex $tomark $j]
8654 if {![info exists hastaggedancestor($dd)]} {
8655 if {[info exists done($dd)]} {
8656 foreach b $arcnos($dd) {
8657 lappend tomark $arcstart($b)
8659 if {[info exists tagloc($dd)]} {
8662 } elseif {[info exists queued($dd)]} {
8665 set hastaggedancestor($dd) 1
8669 if {![info exists queued($d)]} {
8672 if {![info exists hastaggedancestor($d)]} {
8679 foreach id [array names tagloc] {
8680 if {![info exists hastaggedancestor($id)]} {
8681 foreach t $tagloc($id) {
8682 if {[lsearch -exact $tags $t] < 0} {
8688 set t2 [clock clicks -milliseconds]
8691 # remove tags that are descendents of other tags
8692 for {set i 0} {$i < [llength $tags]} {incr i} {
8693 set a [lindex $tags $i]
8694 for {set j 0} {$j < $i} {incr j} {
8695 set b [lindex $tags $j]
8696 set r [anc_or_desc $a $b]
8698 set tags [lreplace $tags $j $j]
8701 } elseif {$r == -1} {
8702 set tags [lreplace $tags $i $i]
8709 if {[array names growing] ne {}} {
8710 # graph isn't finished, need to check if any tag could get
8711 # eclipsed by another tag coming later. Simply ignore any
8712 # tags that could later get eclipsed.
8715 if {[is_certain $t $origid]} {
8719 if {$tags eq $ctags} {
8720 set cached_dtags($origid) $tags
8725 set cached_dtags($origid) $tags
8727 set t3 [clock clicks -milliseconds]
8728 if {0 && $t3 - $t1 >= 100} {
8729 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
8730 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8736 global arcnos arcids arcout arcend arctags idtags allparents
8737 global growing cached_atags
8739 if {![info exists allparents($id)]} {
8742 set t1 [clock clicks -milliseconds]
8744 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8745 # part-way along an arc; check that arc first
8746 set a [lindex $arcnos($id) 0]
8747 if {$arctags($a) ne {}} {
8749 set i [lsearch -exact $arcids($a) $id]
8750 foreach t $arctags($a) {
8751 set j [lsearch -exact $arcids($a) $t]
8757 if {![info exists arcend($a)]} {
8761 if {[info exists idtags($id)]} {
8765 if {[info exists cached_atags($id)]} {
8766 return $cached_atags($id)
8774 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8775 set id [lindex $todo $i]
8777 set td [info exists hastaggeddescendent($id)]
8781 # ignore tags on starting node
8782 if {!$td && $i > 0} {
8783 if {[info exists idtags($id)]} {
8786 } elseif {[info exists cached_atags($id)]} {
8787 set tagloc($id) $cached_atags($id)
8791 foreach a $arcout($id) {
8792 if {!$td && $arctags($a) ne {}} {
8794 if {$arctags($a) ne {}} {
8795 lappend tagloc($id) [lindex $arctags($a) 0]
8798 if {![info exists arcend($a)]} continue
8800 if {$td || $arctags($a) ne {}} {
8801 set tomark [list $d]
8802 for {set j 0} {$j < [llength $tomark]} {incr j} {
8803 set dd [lindex $tomark $j]
8804 if {![info exists hastaggeddescendent($dd)]} {
8805 if {[info exists done($dd)]} {
8806 foreach b $arcout($dd) {
8807 if {[info exists arcend($b)]} {
8808 lappend tomark $arcend($b)
8811 if {[info exists tagloc($dd)]} {
8814 } elseif {[info exists queued($dd)]} {
8817 set hastaggeddescendent($dd) 1
8821 if {![info exists queued($d)]} {
8824 if {![info exists hastaggeddescendent($d)]} {
8830 set t2 [clock clicks -milliseconds]
8833 foreach id [array names tagloc] {
8834 if {![info exists hastaggeddescendent($id)]} {
8835 foreach t $tagloc($id) {
8836 if {[lsearch -exact $tags $t] < 0} {
8843 # remove tags that are ancestors of other tags
8844 for {set i 0} {$i < [llength $tags]} {incr i} {
8845 set a [lindex $tags $i]
8846 for {set j 0} {$j < $i} {incr j} {
8847 set b [lindex $tags $j]
8848 set r [anc_or_desc $a $b]
8850 set tags [lreplace $tags $j $j]
8853 } elseif {$r == 1} {
8854 set tags [lreplace $tags $i $i]
8861 if {[array names growing] ne {}} {
8862 # graph isn't finished, need to check if any tag could get
8863 # eclipsed by another tag coming later. Simply ignore any
8864 # tags that could later get eclipsed.
8867 if {[is_certain $origid $t]} {
8871 if {$tags eq $ctags} {
8872 set cached_atags($origid) $tags
8877 set cached_atags($origid) $tags
8879 set t3 [clock clicks -milliseconds]
8880 if {0 && $t3 - $t1 >= 100} {
8881 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
8882 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8887 # Return the list of IDs that have heads that are descendents of id,
8888 # including id itself if it has a head.
8889 proc descheads {id} {
8890 global arcnos arcstart arcids archeads idheads cached_dheads
8893 if {![info exists allparents($id)]} {
8897 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8898 # part-way along an arc; check it first
8899 set a [lindex $arcnos($id) 0]
8900 if {$archeads($a) ne {}} {
8901 validate_archeads $a
8902 set i [lsearch -exact $arcids($a) $id]
8903 foreach t $archeads($a) {
8904 set j [lsearch -exact $arcids($a) $t]
8909 set id $arcstart($a)
8915 for {set i 0} {$i < [llength $todo]} {incr i} {
8916 set id [lindex $todo $i]
8917 if {[info exists cached_dheads($id)]} {
8918 set ret [concat $ret $cached_dheads($id)]
8920 if {[info exists idheads($id)]} {
8923 foreach a $arcnos($id) {
8924 if {$archeads($a) ne {}} {
8925 validate_archeads $a
8926 if {$archeads($a) ne {}} {
8927 set ret [concat $ret $archeads($a)]
8931 if {![info exists seen($d)]} {
8938 set ret [lsort -unique $ret]
8939 set cached_dheads($origid) $ret
8940 return [concat $ret $aret]
8943 proc addedtag {id} {
8944 global arcnos arcout cached_dtags cached_atags
8946 if {![info exists arcnos($id)]} return
8947 if {![info exists arcout($id)]} {
8948 recalcarc [lindex $arcnos($id) 0]
8950 catch {unset cached_dtags}
8951 catch {unset cached_atags}
8954 proc addedhead {hid head} {
8955 global arcnos arcout cached_dheads
8957 if {![info exists arcnos($hid)]} return
8958 if {![info exists arcout($hid)]} {
8959 recalcarc [lindex $arcnos($hid) 0]
8961 catch {unset cached_dheads}
8964 proc removedhead {hid head} {
8965 global cached_dheads
8967 catch {unset cached_dheads}
8970 proc movedhead {hid head} {
8971 global arcnos arcout cached_dheads
8973 if {![info exists arcnos($hid)]} return
8974 if {![info exists arcout($hid)]} {
8975 recalcarc [lindex $arcnos($hid) 0]
8977 catch {unset cached_dheads}
8980 proc changedrefs {} {
8981 global cached_dheads cached_dtags cached_atags
8982 global arctags archeads arcnos arcout idheads idtags
8984 foreach id [concat [array names idheads] [array names idtags]] {
8985 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
8986 set a [lindex $arcnos($id) 0]
8987 if {![info exists donearc($a)]} {
8993 catch {unset cached_dtags}
8994 catch {unset cached_atags}
8995 catch {unset cached_dheads}
8998 proc rereadrefs {} {
8999 global idtags idheads idotherrefs mainheadid
9001 set refids [concat [array names idtags] \
9002 [array names idheads] [array names idotherrefs]]
9003 foreach id $refids {
9004 if {![info exists ref($id)]} {
9005 set ref($id) [listrefs $id]
9008 set oldmainhead $mainheadid
9011 set refids [lsort -unique [concat $refids [array names idtags] \
9012 [array names idheads] [array names idotherrefs]]]
9013 foreach id $refids {
9014 set v [listrefs $id]
9015 if {![info exists ref($id)] || $ref($id) != $v ||
9016 ($id eq $oldmainhead && $id ne $mainheadid) ||
9017 ($id eq $mainheadid && $id ne $oldmainhead)} {
9024 proc listrefs {id} {
9025 global idtags idheads idotherrefs
9028 if {[info exists idtags($id)]} {
9032 if {[info exists idheads($id)]} {
9036 if {[info exists idotherrefs($id)]} {
9037 set z $idotherrefs($id)
9039 return [list $x $y $z]
9042 proc showtag {tag isnew} {
9043 global ctext tagcontents tagids linknum tagobjid
9046 addtohistory [list showtag $tag 0]
9048 $ctext conf -state normal
9052 if {![info exists tagcontents($tag)]} {
9054 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9057 if {[info exists tagcontents($tag)]} {
9058 set text $tagcontents($tag)
9060 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9062 appendwithlinks $text {}
9063 $ctext conf -state disabled
9075 if {[info exists gitktmpdir]} {
9076 catch {file delete -force $gitktmpdir}
9080 proc mkfontdisp {font top which} {
9081 global fontattr fontpref $font
9083 set fontpref($font) [set $font]
9084 button $top.${font}but -text $which -font optionfont \
9085 -command [list choosefont $font $which]
9086 label $top.$font -relief flat -font $font \
9087 -text $fontattr($font,family) -justify left
9088 grid x $top.${font}but $top.$font -sticky w
9091 proc choosefont {font which} {
9092 global fontparam fontlist fonttop fontattr
9094 set fontparam(which) $which
9095 set fontparam(font) $font
9096 set fontparam(family) [font actual $font -family]
9097 set fontparam(size) $fontattr($font,size)
9098 set fontparam(weight) $fontattr($font,weight)
9099 set fontparam(slant) $fontattr($font,slant)
9102 if {![winfo exists $top]} {
9104 eval font config sample [font actual $font]
9106 wm title $top [mc "Gitk font chooser"]
9107 label $top.l -textvariable fontparam(which)
9108 pack $top.l -side top
9109 set fontlist [lsort [font families]]
9111 listbox $top.f.fam -listvariable fontlist \
9112 -yscrollcommand [list $top.f.sb set]
9113 bind $top.f.fam <<ListboxSelect>> selfontfam
9114 scrollbar $top.f.sb -command [list $top.f.fam yview]
9115 pack $top.f.sb -side right -fill y
9116 pack $top.f.fam -side left -fill both -expand 1
9117 pack $top.f -side top -fill both -expand 1
9119 spinbox $top.g.size -from 4 -to 40 -width 4 \
9120 -textvariable fontparam(size) \
9121 -validatecommand {string is integer -strict %s}
9122 checkbutton $top.g.bold -padx 5 \
9123 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9124 -variable fontparam(weight) -onvalue bold -offvalue normal
9125 checkbutton $top.g.ital -padx 5 \
9126 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9127 -variable fontparam(slant) -onvalue italic -offvalue roman
9128 pack $top.g.size $top.g.bold $top.g.ital -side left
9129 pack $top.g -side top
9130 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9132 $top.c create text 100 25 -anchor center -text $which -font sample \
9133 -fill black -tags text
9134 bind $top.c <Configure> [list centertext $top.c]
9135 pack $top.c -side top -fill x
9137 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9138 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9139 grid $top.buts.ok $top.buts.can
9140 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9141 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9142 pack $top.buts -side bottom -fill x
9143 trace add variable fontparam write chg_fontparam
9146 $top.c itemconf text -text $which
9148 set i [lsearch -exact $fontlist $fontparam(family)]
9150 $top.f.fam selection set $i
9155 proc centertext {w} {
9156 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9160 global fontparam fontpref prefstop
9162 set f $fontparam(font)
9163 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9164 if {$fontparam(weight) eq "bold"} {
9165 lappend fontpref($f) "bold"
9167 if {$fontparam(slant) eq "italic"} {
9168 lappend fontpref($f) "italic"
9171 $w conf -text $fontparam(family) -font $fontpref($f)
9177 global fonttop fontparam
9179 if {[info exists fonttop]} {
9180 catch {destroy $fonttop}
9181 catch {font delete sample}
9187 proc selfontfam {} {
9188 global fonttop fontparam
9190 set i [$fonttop.f.fam curselection]
9192 set fontparam(family) [$fonttop.f.fam get $i]
9196 proc chg_fontparam {v sub op} {
9199 font config sample -$sub $fontparam($sub)
9203 global maxwidth maxgraphpct
9204 global oldprefs prefstop showneartags showlocalchanges
9205 global bgcolor fgcolor ctext diffcolors selectbgcolor
9206 global tabstop limitdiffs autoselect extdifftool
9210 if {[winfo exists $top]} {
9214 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9215 limitdiffs tabstop} {
9216 set oldprefs($v) [set $v]
9219 wm title $top [mc "Gitk preferences"]
9220 label $top.ldisp -text [mc "Commit list display options"]
9221 grid $top.ldisp - -sticky w -pady 10
9222 label $top.spacer -text " "
9223 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9225 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9226 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9227 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9229 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9230 grid x $top.maxpctl $top.maxpct -sticky w
9231 frame $top.showlocal
9232 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9233 checkbutton $top.showlocal.b -variable showlocalchanges
9234 pack $top.showlocal.b $top.showlocal.l -side left
9235 grid x $top.showlocal -sticky w
9236 frame $top.autoselect
9237 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9238 checkbutton $top.autoselect.b -variable autoselect
9239 pack $top.autoselect.b $top.autoselect.l -side left
9240 grid x $top.autoselect -sticky w
9242 label $top.ddisp -text [mc "Diff display options"]
9243 grid $top.ddisp - -sticky w -pady 10
9244 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9245 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9246 grid x $top.tabstopl $top.tabstop -sticky w
9248 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9249 checkbutton $top.ntag.b -variable showneartags
9250 pack $top.ntag.b $top.ntag.l -side left
9251 grid x $top.ntag -sticky w
9253 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9254 checkbutton $top.ldiff.b -variable limitdiffs
9255 pack $top.ldiff.b $top.ldiff.l -side left
9256 grid x $top.ldiff -sticky w
9258 entry $top.extdifft -textvariable extdifftool
9260 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
9262 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
9263 -command choose_extdiff
9264 pack $top.extdifff.l $top.extdifff.b -side left
9265 grid x $top.extdifff $top.extdifft -sticky w
9267 label $top.cdisp -text [mc "Colors: press to choose"]
9268 grid $top.cdisp - -sticky w -pady 10
9269 label $top.bg -padx 40 -relief sunk -background $bgcolor
9270 button $top.bgbut -text [mc "Background"] -font optionfont \
9271 -command [list choosecolor bgcolor {} $top.bg background setbg]
9272 grid x $top.bgbut $top.bg -sticky w
9273 label $top.fg -padx 40 -relief sunk -background $fgcolor
9274 button $top.fgbut -text [mc "Foreground"] -font optionfont \
9275 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
9276 grid x $top.fgbut $top.fg -sticky w
9277 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
9278 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
9279 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
9280 [list $ctext tag conf d0 -foreground]]
9281 grid x $top.diffoldbut $top.diffold -sticky w
9282 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
9283 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
9284 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
9285 [list $ctext tag conf d1 -foreground]]
9286 grid x $top.diffnewbut $top.diffnew -sticky w
9287 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
9288 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
9289 -command [list choosecolor diffcolors 2 $top.hunksep \
9290 "diff hunk header" \
9291 [list $ctext tag conf hunksep -foreground]]
9292 grid x $top.hunksepbut $top.hunksep -sticky w
9293 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
9294 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
9295 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
9296 grid x $top.selbgbut $top.selbgsep -sticky w
9298 label $top.cfont -text [mc "Fonts: press to choose"]
9299 grid $top.cfont - -sticky w -pady 10
9300 mkfontdisp mainfont $top [mc "Main font"]
9301 mkfontdisp textfont $top [mc "Diff display font"]
9302 mkfontdisp uifont $top [mc "User interface font"]
9305 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
9306 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
9307 grid $top.buts.ok $top.buts.can
9308 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9309 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9310 grid $top.buts - - -pady 10 -sticky ew
9311 bind $top <Visibility> "focus $top.buts.ok"
9314 proc choose_extdiff {} {
9317 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
9319 set extdifftool $prog
9323 proc choosecolor {v vi w x cmd} {
9326 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
9327 -title [mc "Gitk: choose color for %s" $x]]
9328 if {$c eq {}} return
9329 $w conf -background $c
9335 global bglist cflist
9337 $w configure -selectbackground $c
9339 $cflist tag configure highlight \
9340 -background [$cflist cget -selectbackground]
9341 allcanvs itemconf secsel -fill $c
9348 $w conf -background $c
9356 $w conf -foreground $c
9358 allcanvs itemconf text -fill $c
9359 $canv itemconf circle -outline $c
9363 global oldprefs prefstop
9365 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9366 limitdiffs tabstop} {
9368 set $v $oldprefs($v)
9370 catch {destroy $prefstop}
9376 global maxwidth maxgraphpct
9377 global oldprefs prefstop showneartags showlocalchanges
9378 global fontpref mainfont textfont uifont
9379 global limitdiffs treediffs
9381 catch {destroy $prefstop}
9385 if {$mainfont ne $fontpref(mainfont)} {
9386 set mainfont $fontpref(mainfont)
9387 parsefont mainfont $mainfont
9388 eval font configure mainfont [fontflags mainfont]
9389 eval font configure mainfontbold [fontflags mainfont 1]
9393 if {$textfont ne $fontpref(textfont)} {
9394 set textfont $fontpref(textfont)
9395 parsefont textfont $textfont
9396 eval font configure textfont [fontflags textfont]
9397 eval font configure textfontbold [fontflags textfont 1]
9399 if {$uifont ne $fontpref(uifont)} {
9400 set uifont $fontpref(uifont)
9401 parsefont uifont $uifont
9402 eval font configure uifont [fontflags uifont]
9405 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
9406 if {$showlocalchanges} {
9412 if {$limitdiffs != $oldprefs(limitdiffs)} {
9413 # treediffs elements are limited by path
9414 catch {unset treediffs}
9416 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
9417 || $maxgraphpct != $oldprefs(maxgraphpct)} {
9419 } elseif {$showneartags != $oldprefs(showneartags) ||
9420 $limitdiffs != $oldprefs(limitdiffs)} {
9425 proc formatdate {d} {
9426 global datetimeformat
9428 set d [clock format $d -format $datetimeformat]
9433 # This list of encoding names and aliases is distilled from
9434 # http://www.iana.org/assignments/character-sets.
9435 # Not all of them are supported by Tcl.
9436 set encoding_aliases {
9437 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
9438 ISO646-US US-ASCII us IBM367 cp367 csASCII }
9439 { ISO-10646-UTF-1 csISO10646UTF1 }
9440 { ISO_646.basic:1983 ref csISO646basic1983 }
9441 { INVARIANT csINVARIANT }
9442 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
9443 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
9444 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
9445 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
9446 { NATS-DANO iso-ir-9-1 csNATSDANO }
9447 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
9448 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
9449 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
9450 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
9451 { ISO-2022-KR csISO2022KR }
9453 { ISO-2022-JP csISO2022JP }
9454 { ISO-2022-JP-2 csISO2022JP2 }
9455 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
9457 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
9458 { IT iso-ir-15 ISO646-IT csISO15Italian }
9459 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
9460 { ES iso-ir-17 ISO646-ES csISO17Spanish }
9461 { greek7-old iso-ir-18 csISO18Greek7Old }
9462 { latin-greek iso-ir-19 csISO19LatinGreek }
9463 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
9464 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
9465 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
9466 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
9467 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
9468 { BS_viewdata iso-ir-47 csISO47BSViewdata }
9469 { INIS iso-ir-49 csISO49INIS }
9470 { INIS-8 iso-ir-50 csISO50INIS8 }
9471 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
9472 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
9473 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
9474 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
9475 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
9476 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
9478 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
9479 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
9480 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
9481 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
9482 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
9483 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
9484 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
9485 { greek7 iso-ir-88 csISO88Greek7 }
9486 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
9487 { iso-ir-90 csISO90 }
9488 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
9489 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
9490 csISO92JISC62991984b }
9491 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
9492 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
9493 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
9494 csISO95JIS62291984handadd }
9495 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
9496 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
9497 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
9498 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
9500 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
9501 { T.61-7bit iso-ir-102 csISO102T617bit }
9502 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
9503 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
9504 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
9505 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
9506 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
9507 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
9508 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
9509 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
9510 arabic csISOLatinArabic }
9511 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
9512 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
9513 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
9514 greek greek8 csISOLatinGreek }
9515 { T.101-G2 iso-ir-128 csISO128T101G2 }
9516 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
9518 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
9519 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
9520 { CSN_369103 iso-ir-139 csISO139CSN369103 }
9521 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
9522 { ISO_6937-2-add iso-ir-142 csISOTextComm }
9523 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
9524 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
9525 csISOLatinCyrillic }
9526 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
9527 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
9528 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
9529 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
9530 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
9531 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
9532 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
9533 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
9534 { ISO_10367-box iso-ir-155 csISO10367Box }
9535 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
9536 { latin-lap lap iso-ir-158 csISO158Lap }
9537 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
9538 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
9541 { JIS_X0201 X0201 csHalfWidthKatakana }
9542 { KSC5636 ISO646-KR csKSC5636 }
9543 { ISO-10646-UCS-2 csUnicode }
9544 { ISO-10646-UCS-4 csUCS4 }
9545 { DEC-MCS dec csDECMCS }
9546 { hp-roman8 roman8 r8 csHPRoman8 }
9547 { macintosh mac csMacintosh }
9548 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
9550 { IBM038 EBCDIC-INT cp038 csIBM038 }
9551 { IBM273 CP273 csIBM273 }
9552 { IBM274 EBCDIC-BE CP274 csIBM274 }
9553 { IBM275 EBCDIC-BR cp275 csIBM275 }
9554 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
9555 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
9556 { IBM280 CP280 ebcdic-cp-it csIBM280 }
9557 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
9558 { IBM284 CP284 ebcdic-cp-es csIBM284 }
9559 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
9560 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
9561 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
9562 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
9563 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
9564 { IBM424 cp424 ebcdic-cp-he csIBM424 }
9565 { IBM437 cp437 437 csPC8CodePage437 }
9566 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
9567 { IBM775 cp775 csPC775Baltic }
9568 { IBM850 cp850 850 csPC850Multilingual }
9569 { IBM851 cp851 851 csIBM851 }
9570 { IBM852 cp852 852 csPCp852 }
9571 { IBM855 cp855 855 csIBM855 }
9572 { IBM857 cp857 857 csIBM857 }
9573 { IBM860 cp860 860 csIBM860 }
9574 { IBM861 cp861 861 cp-is csIBM861 }
9575 { IBM862 cp862 862 csPC862LatinHebrew }
9576 { IBM863 cp863 863 csIBM863 }
9577 { IBM864 cp864 csIBM864 }
9578 { IBM865 cp865 865 csIBM865 }
9579 { IBM866 cp866 866 csIBM866 }
9580 { IBM868 CP868 cp-ar csIBM868 }
9581 { IBM869 cp869 869 cp-gr csIBM869 }
9582 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
9583 { IBM871 CP871 ebcdic-cp-is csIBM871 }
9584 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
9585 { IBM891 cp891 csIBM891 }
9586 { IBM903 cp903 csIBM903 }
9587 { IBM904 cp904 904 csIBBM904 }
9588 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
9589 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
9590 { IBM1026 CP1026 csIBM1026 }
9591 { EBCDIC-AT-DE csIBMEBCDICATDE }
9592 { EBCDIC-AT-DE-A csEBCDICATDEA }
9593 { EBCDIC-CA-FR csEBCDICCAFR }
9594 { EBCDIC-DK-NO csEBCDICDKNO }
9595 { EBCDIC-DK-NO-A csEBCDICDKNOA }
9596 { EBCDIC-FI-SE csEBCDICFISE }
9597 { EBCDIC-FI-SE-A csEBCDICFISEA }
9598 { EBCDIC-FR csEBCDICFR }
9599 { EBCDIC-IT csEBCDICIT }
9600 { EBCDIC-PT csEBCDICPT }
9601 { EBCDIC-ES csEBCDICES }
9602 { EBCDIC-ES-A csEBCDICESA }
9603 { EBCDIC-ES-S csEBCDICESS }
9604 { EBCDIC-UK csEBCDICUK }
9605 { EBCDIC-US csEBCDICUS }
9606 { UNKNOWN-8BIT csUnknown8BiT }
9607 { MNEMONIC csMnemonic }
9612 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
9613 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
9614 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
9615 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
9616 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
9617 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
9618 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
9619 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
9620 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
9621 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
9622 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
9623 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
9624 { IBM1047 IBM-1047 }
9625 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
9626 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
9627 { UNICODE-1-1 csUnicode11 }
9630 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
9631 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
9633 { ISO-8859-15 ISO_8859-15 Latin-9 }
9634 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
9635 { GBK CP936 MS936 windows-936 }
9636 { JIS_Encoding csJISEncoding }
9637 { Shift_JIS MS_Kanji csShiftJIS }
9638 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
9640 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
9641 { ISO-10646-UCS-Basic csUnicodeASCII }
9642 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
9643 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
9644 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
9645 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
9646 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
9647 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
9648 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
9649 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
9650 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
9651 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
9652 { Adobe-Standard-Encoding csAdobeStandardEncoding }
9653 { Ventura-US csVenturaUS }
9654 { Ventura-International csVenturaInternational }
9655 { PC8-Danish-Norwegian csPC8DanishNorwegian }
9656 { PC8-Turkish csPC8Turkish }
9657 { IBM-Symbols csIBMSymbols }
9658 { IBM-Thai csIBMThai }
9659 { HP-Legal csHPLegal }
9660 { HP-Pi-font csHPPiFont }
9661 { HP-Math8 csHPMath8 }
9662 { Adobe-Symbol-Encoding csHPPSMath }
9663 { HP-DeskTop csHPDesktop }
9664 { Ventura-Math csVenturaMath }
9665 { Microsoft-Publishing csMicrosoftPublishing }
9666 { Windows-31J csWindows31J }
9671 proc tcl_encoding {enc} {
9672 global encoding_aliases
9673 set names [encoding names]
9674 set lcnames [string tolower $names]
9675 set enc [string tolower $enc]
9676 set i [lsearch -exact $lcnames $enc]
9678 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
9679 if {[regsub {^iso[-_]} $enc iso encx]} {
9680 set i [lsearch -exact $lcnames $encx]
9684 foreach l $encoding_aliases {
9685 set ll [string tolower $l]
9686 if {[lsearch -exact $ll $enc] < 0} continue
9687 # look through the aliases for one that tcl knows about
9689 set i [lsearch -exact $lcnames $e]
9691 if {[regsub {^iso[-_]} $e iso ex]} {
9692 set i [lsearch -exact $lcnames $ex]
9701 return [lindex $names $i]
9706 # First check that Tcl/Tk is recent enough
9707 if {[catch {package require Tk 8.4} err]} {
9708 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
9709 Gitk requires at least Tcl/Tk 8.4."]
9714 set wrcomcmd "git diff-tree --stdin -p --pretty"
9718 set gitencoding [exec git config --get i18n.commitencoding]
9720 if {$gitencoding == ""} {
9721 set gitencoding "utf-8"
9723 set tclencoding [tcl_encoding $gitencoding]
9724 if {$tclencoding == {}} {
9725 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
9728 set mainfont {Helvetica 9}
9729 set textfont {Courier 9}
9730 set uifont {Helvetica 9 bold}
9732 set findmergefiles 0
9740 set cmitmode "patch"
9741 set wrapcomment "none"
9745 set showlocalchanges 1
9747 set datetimeformat "%Y-%m-%d %H:%M:%S"
9750 set extdifftool "meld"
9752 set colors {green red blue magenta darkgrey brown orange}
9755 set diffcolors {red "#00a000" blue}
9758 set selectbgcolor gray85
9760 ## For msgcat loading, first locate the installation location.
9761 if { [info exists ::env(GITK_MSGSDIR)] } {
9762 ## Msgsdir was manually set in the environment.
9763 set gitk_msgsdir $::env(GITK_MSGSDIR)
9765 ## Let's guess the prefix from argv0.
9766 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
9767 set gitk_libdir [file join $gitk_prefix share gitk lib]
9768 set gitk_msgsdir [file join $gitk_libdir msgs]
9772 ## Internationalization (i18n) through msgcat and gettext. See
9773 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
9774 package require msgcat
9775 namespace import ::msgcat::mc
9776 ## And eventually load the actual message catalog
9777 ::msgcat::mcload $gitk_msgsdir
9779 catch {source ~/.gitk}
9781 font create optionfont -family sans-serif -size -12
9783 parsefont mainfont $mainfont
9784 eval font create mainfont [fontflags mainfont]
9785 eval font create mainfontbold [fontflags mainfont 1]
9787 parsefont textfont $textfont
9788 eval font create textfont [fontflags textfont]
9789 eval font create textfontbold [fontflags textfont 1]
9791 parsefont uifont $uifont
9792 eval font create uifont [fontflags uifont]
9796 # check that we can find a .git directory somewhere...
9797 if {[catch {set gitdir [gitdir]}]} {
9798 show_error {} . [mc "Cannot find a git repository here."]
9801 if {![file isdirectory $gitdir]} {
9802 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
9807 set cmdline_files {}
9809 set revtreeargscmd {}
9811 switch -glob -- $arg {
9814 set cmdline_files [lrange $argv [expr {$i + 1}] end]
9818 set revtreeargscmd [string range $arg 10 end]
9821 lappend revtreeargs $arg
9827 if {$i >= [llength $argv] && $revtreeargs ne {}} {
9828 # no -- on command line, but some arguments (other than --argscmd)
9830 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
9831 set cmdline_files [split $f "\n"]
9832 set n [llength $cmdline_files]
9833 set revtreeargs [lrange $revtreeargs 0 end-$n]
9834 # Unfortunately git rev-parse doesn't produce an error when
9835 # something is both a revision and a filename. To be consistent
9836 # with git log and git rev-list, check revtreeargs for filenames.
9837 foreach arg $revtreeargs {
9838 if {[file exists $arg]} {
9839 show_error {} . [mc "Ambiguous argument '%s': both revision\
9845 # unfortunately we get both stdout and stderr in $err,
9846 # so look for "fatal:".
9847 set i [string first "fatal:" $err]
9849 set err [string range $err [expr {$i + 6}] end]
9851 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
9856 set nullid "0000000000000000000000000000000000000000"
9857 set nullid2 "0000000000000000000000000000000000000001"
9858 set nullfile "/dev/null"
9860 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
9867 set highlight_paths {}
9869 set searchdirn -forwards
9873 set markingmatches 0
9874 set linkentercount 0
9875 set need_redisplay 0
9882 set selectedhlview [mc "None"]
9883 set highlight_related [mc "None"]
9884 set highlight_files {}
9888 set viewargscmd(0) {}
9897 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
9900 # wait for the window to become visible
9902 wm title . "[file tail $argv0]: [file tail [pwd]]"
9905 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
9906 # create a view for the files/dirs specified on the command line
9910 set viewname(1) [mc "Command line"]
9911 set viewfiles(1) $cmdline_files
9912 set viewargs(1) $revtreeargs
9913 set viewargscmd(1) $revtreeargscmd
9917 .bar.view entryconf [mc "Edit view..."] -state normal
9918 .bar.view entryconf [mc "Delete view"] -state normal
9921 if {[info exists permviews]} {
9922 foreach v $permviews {
9925 set viewname($n) [lindex $v 0]
9926 set viewfiles($n) [lindex $v 1]
9927 set viewargs($n) [lindex $v 2]
9928 set viewargscmd($n) [lindex $v 3]