Merge branch 'master' of git://repo.or.cz/alt-git
[4msysgit-hv.git] / gitk-git / gitk
blobb064a4e58a7c88996d08c2777e07d0c6e6f65f4d
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
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.
10 proc gitdir {} {
11 global env
12 if {[info exists env(GIT_DIR)]} {
13 return $env(GIT_DIR)
14 } else {
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.
24 proc run args {
25 global isonrunq runq currunq
27 set script $args
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {} && ![info exists currunq]} {
30 after idle dorunq
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} {
41 global runq currunq
43 fileevent $fd readable {}
44 if {$runq eq {} && ![info exists currunq]} {
45 after idle dorunq
47 lappend runq [list $fd $script]
50 proc nukefile {fd} {
51 global runq
53 for {set i 0} {$i < [llength $runq]} {} {
54 if {[lindex $runq $i 0] eq $fd} {
55 set runq [lreplace $runq $i $i]
56 } else {
57 incr i
62 proc dorunq {} {
63 global isonrunq runq currunq
65 set tstart [clock clicks -milliseconds]
66 set t0 $tstart
67 while {[llength $runq] > 0} {
68 set fd [lindex $runq 0 0]
69 set script [lindex $runq 0 1]
70 set currunq [lindex $runq 0]
71 set runq [lrange $runq 1 end]
72 set repeat [eval $script]
73 unset currunq
74 set t1 [clock clicks -milliseconds]
75 set t [expr {$t1 - $t0}]
76 if {$repeat ne {} && $repeat} {
77 if {$fd eq {} || $repeat == 2} {
78 # script returns 1 if it wants to be readded
79 # file readers return 2 if they could do more straight away
80 lappend runq [list $fd $script]
81 } else {
82 fileevent $fd readable [list filereadable $fd $script]
84 } elseif {$fd eq {}} {
85 unset isonrunq($script)
87 set t0 $t1
88 if {$t1 - $tstart >= 80} break
90 if {$runq ne {}} {
91 after idle dorunq
95 proc reg_instance {fd} {
96 global commfd leftover loginstance
98 set i [incr loginstance]
99 set commfd($i) $fd
100 set leftover($i) {}
101 return $i
104 proc unmerged_files {files} {
105 global nr_unmerged
107 # find the list of unmerged files
108 set mlist {}
109 set nr_unmerged 0
110 if {[catch {
111 set fd [open "| git ls-files -u" r]
112 } err]} {
113 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
114 exit 1
116 while {[gets $fd line] >= 0} {
117 set i [string first "\t" $line]
118 if {$i < 0} continue
119 set fname [string range $line [expr {$i+1}] end]
120 if {[lsearch -exact $mlist $fname] >= 0} continue
121 incr nr_unmerged
122 if {$files eq {} || [path_filter $files $fname]} {
123 lappend mlist $fname
126 catch {close $fd}
127 return $mlist
130 proc parseviewargs {n arglist} {
131 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
133 set vdatemode($n) 0
134 set vmergeonly($n) 0
135 set glflags {}
136 set diffargs {}
137 set nextisval 0
138 set revargs {}
139 set origargs $arglist
140 set allknown 1
141 set filtered 0
142 set i -1
143 foreach arg $arglist {
144 incr i
145 if {$nextisval} {
146 lappend glflags $arg
147 set nextisval 0
148 continue
150 switch -glob -- $arg {
151 "-d" -
152 "--date-order" {
153 set vdatemode($n) 1
154 # remove from origargs in case we hit an unknown option
155 set origargs [lreplace $origargs $i $i]
156 incr i -1
158 # These request or affect diff output, which we don't want.
159 # Some could be used to set our defaults for diff display.
160 "-[puabwcrRBMC]" -
161 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
162 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
163 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
164 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
165 "--ignore-space-change" - "-U*" - "--unified=*" {
166 lappend diffargs $arg
168 # These cause our parsing of git log's output to fail, or else
169 # they're options we want to set ourselves, so ignore them.
170 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
171 "--name-only" - "--name-status" - "--color" - "--color-words" -
172 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
173 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
174 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
175 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
176 "--objects" - "--objects-edge" - "--reverse" {
178 # These are harmless, and some are even useful
179 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
180 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
181 "--full-history" - "--dense" - "--sparse" -
182 "--follow" - "--left-right" - "--encoding=*" {
183 lappend glflags $arg
185 # These mean that we get a subset of the commits
186 "--diff-filter=*" - "--no-merges" - "--unpacked" -
187 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
188 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
189 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
190 "--remove-empty" - "--first-parent" - "--cherry-pick" -
191 "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
192 set filtered 1
193 lappend glflags $arg
195 # This appears to be the only one that has a value as a
196 # separate word following it
197 "-n" {
198 set filtered 1
199 set nextisval 1
200 lappend glflags $arg
202 "--not" {
203 set notflag [expr {!$notflag}]
204 lappend revargs $arg
206 "--all" {
207 lappend revargs $arg
209 "--merge" {
210 set vmergeonly($n) 1
211 # git rev-parse doesn't understand --merge
212 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
214 # Other flag arguments including -<n>
215 "-*" {
216 if {[string is digit -strict [string range $arg 1 end]]} {
217 set filtered 1
218 } else {
219 # a flag argument that we don't recognize;
220 # that means we can't optimize
221 set allknown 0
223 lappend glflags $arg
225 # Non-flag arguments specify commits or ranges of commits
226 default {
227 if {[string match "*...*" $arg]} {
228 lappend revargs --gitk-symmetric-diff-marker
230 lappend revargs $arg
234 set vdflags($n) $diffargs
235 set vflags($n) $glflags
236 set vrevs($n) $revargs
237 set vfiltered($n) $filtered
238 set vorigargs($n) $origargs
239 return $allknown
242 proc parseviewrevs {view revs} {
243 global vposids vnegids
245 if {$revs eq {}} {
246 set revs HEAD
248 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
249 # we get stdout followed by stderr in $err
250 # for an unknown rev, git rev-parse echoes it and then errors out
251 set errlines [split $err "\n"]
252 set badrev {}
253 for {set l 0} {$l < [llength $errlines]} {incr l} {
254 set line [lindex $errlines $l]
255 if {!([string length $line] == 40 && [string is xdigit $line])} {
256 if {[string match "fatal:*" $line]} {
257 if {[string match "fatal: ambiguous argument*" $line]
258 && $badrev ne {}} {
259 if {[llength $badrev] == 1} {
260 set err "unknown revision $badrev"
261 } else {
262 set err "unknown revisions: [join $badrev ", "]"
264 } else {
265 set err [join [lrange $errlines $l end] "\n"]
267 break
269 lappend badrev $line
272 error_popup "[mc "Error parsing revisions:"] $err"
273 return {}
275 set ret {}
276 set pos {}
277 set neg {}
278 set sdm 0
279 foreach id [split $ids "\n"] {
280 if {$id eq "--gitk-symmetric-diff-marker"} {
281 set sdm 4
282 } elseif {[string match "^*" $id]} {
283 if {$sdm != 1} {
284 lappend ret $id
285 if {$sdm == 3} {
286 set sdm 0
289 lappend neg [string range $id 1 end]
290 } else {
291 if {$sdm != 2} {
292 lappend ret $id
293 } else {
294 lset ret end [lindex $ret end]...$id
296 lappend pos $id
298 incr sdm -1
300 set vposids($view) $pos
301 set vnegids($view) $neg
302 return $ret
305 # Start off a git log process and arrange to read its output
306 proc start_rev_list {view} {
307 global startmsecs commitidx viewcomplete curview
308 global tclencoding
309 global viewargs viewargscmd viewfiles vfilelimit
310 global showlocalchanges
311 global viewactive viewinstances vmergeonly
312 global mainheadid
313 global vcanopt vflags vrevs vorigargs
315 set startmsecs [clock clicks -milliseconds]
316 set commitidx($view) 0
317 # these are set this way for the error exits
318 set viewcomplete($view) 1
319 set viewactive($view) 0
320 varcinit $view
322 set args $viewargs($view)
323 if {$viewargscmd($view) ne {}} {
324 if {[catch {
325 set str [exec sh -c $viewargscmd($view)]
326 } err]} {
327 error_popup "[mc "Error executing --argscmd command:"] $err"
328 return 0
330 set args [concat $args [split $str "\n"]]
332 set vcanopt($view) [parseviewargs $view $args]
334 set files $viewfiles($view)
335 if {$vmergeonly($view)} {
336 set files [unmerged_files $files]
337 if {$files eq {}} {
338 global nr_unmerged
339 if {$nr_unmerged == 0} {
340 error_popup [mc "No files selected: --merge specified but\
341 no files are unmerged."]
342 } else {
343 error_popup [mc "No files selected: --merge specified but\
344 no unmerged files are within file limit."]
346 return 0
349 set vfilelimit($view) $files
351 if {$vcanopt($view)} {
352 set revs [parseviewrevs $view $vrevs($view)]
353 if {$revs eq {}} {
354 return 0
356 set args [concat $vflags($view) $revs]
357 } else {
358 set args $vorigargs($view)
361 if {[catch {
362 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
363 --boundary $args "--" $files] r]
364 } err]} {
365 error_popup "[mc "Error executing git log:"] $err"
366 return 0
368 set i [reg_instance $fd]
369 set viewinstances($view) [list $i]
370 if {$showlocalchanges && $mainheadid ne {}} {
371 interestedin $mainheadid dodiffindex
373 fconfigure $fd -blocking 0 -translation lf -eofchar {}
374 if {$tclencoding != {}} {
375 fconfigure $fd -encoding $tclencoding
377 filerun $fd [list getcommitlines $fd $i $view 0]
378 nowbusy $view [mc "Reading"]
379 set viewcomplete($view) 0
380 set viewactive($view) 1
381 return 1
384 proc stop_instance {inst} {
385 global commfd leftover
387 set fd $commfd($inst)
388 catch {
389 set pid [pid $fd]
391 if {$::tcl_platform(platform) eq {windows}} {
392 exec kill -f $pid
393 } else {
394 exec kill $pid
397 catch {close $fd}
398 nukefile $fd
399 unset commfd($inst)
400 unset leftover($inst)
403 proc stop_backends {} {
404 global commfd
406 foreach inst [array names commfd] {
407 stop_instance $inst
411 proc stop_rev_list {view} {
412 global viewinstances
414 foreach inst $viewinstances($view) {
415 stop_instance $inst
417 set viewinstances($view) {}
420 proc reset_pending_select {selid} {
421 global pending_select mainheadid selectheadid
423 if {$selid ne {}} {
424 set pending_select $selid
425 } elseif {$selectheadid ne {}} {
426 set pending_select $selectheadid
427 } else {
428 set pending_select $mainheadid
432 proc getcommits {selid} {
433 global canv curview need_redisplay viewactive
435 initlayout
436 if {[start_rev_list $curview]} {
437 reset_pending_select $selid
438 show_status [mc "Reading commits..."]
439 set need_redisplay 1
440 } else {
441 show_status [mc "No commits selected"]
445 proc updatecommits {} {
446 global curview vcanopt vorigargs vfilelimit viewinstances
447 global viewactive viewcomplete tclencoding
448 global startmsecs showneartags showlocalchanges
449 global mainheadid pending_select
450 global isworktree
451 global varcid vposids vnegids vflags vrevs
453 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
454 set oldmainid $mainheadid
455 rereadrefs
456 if {$showlocalchanges} {
457 if {$mainheadid ne $oldmainid} {
458 dohidelocalchanges
460 if {[commitinview $mainheadid $curview]} {
461 dodiffindex
464 set view $curview
465 if {$vcanopt($view)} {
466 set oldpos $vposids($view)
467 set oldneg $vnegids($view)
468 set revs [parseviewrevs $view $vrevs($view)]
469 if {$revs eq {}} {
470 return
472 # note: getting the delta when negative refs change is hard,
473 # and could require multiple git log invocations, so in that
474 # case we ask git log for all the commits (not just the delta)
475 if {$oldneg eq $vnegids($view)} {
476 set newrevs {}
477 set npos 0
478 # take out positive refs that we asked for before or
479 # that we have already seen
480 foreach rev $revs {
481 if {[string length $rev] == 40} {
482 if {[lsearch -exact $oldpos $rev] < 0
483 && ![info exists varcid($view,$rev)]} {
484 lappend newrevs $rev
485 incr npos
487 } else {
488 lappend $newrevs $rev
491 if {$npos == 0} return
492 set revs $newrevs
493 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
495 set args [concat $vflags($view) $revs --not $oldpos]
496 } else {
497 set args $vorigargs($view)
499 if {[catch {
500 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
501 --boundary $args "--" $vfilelimit($view)] r]
502 } err]} {
503 error_popup "[mc "Error executing git log:"] $err"
504 return
506 if {$viewactive($view) == 0} {
507 set startmsecs [clock clicks -milliseconds]
509 set i [reg_instance $fd]
510 lappend viewinstances($view) $i
511 fconfigure $fd -blocking 0 -translation lf -eofchar {}
512 if {$tclencoding != {}} {
513 fconfigure $fd -encoding $tclencoding
515 filerun $fd [list getcommitlines $fd $i $view 1]
516 incr viewactive($view)
517 set viewcomplete($view) 0
518 reset_pending_select {}
519 nowbusy $view "Reading"
520 if {$showneartags} {
521 getallcommits
525 proc reloadcommits {} {
526 global curview viewcomplete selectedline currentid thickerline
527 global showneartags treediffs commitinterest cached_commitrow
528 global targetid
530 set selid {}
531 if {$selectedline ne {}} {
532 set selid $currentid
535 if {!$viewcomplete($curview)} {
536 stop_rev_list $curview
538 resetvarcs $curview
539 set selectedline {}
540 catch {unset currentid}
541 catch {unset thickerline}
542 catch {unset treediffs}
543 readrefs
544 changedrefs
545 if {$showneartags} {
546 getallcommits
548 clear_display
549 catch {unset commitinterest}
550 catch {unset cached_commitrow}
551 catch {unset targetid}
552 setcanvscroll
553 getcommits $selid
554 return 0
557 # This makes a string representation of a positive integer which
558 # sorts as a string in numerical order
559 proc strrep {n} {
560 if {$n < 16} {
561 return [format "%x" $n]
562 } elseif {$n < 256} {
563 return [format "x%.2x" $n]
564 } elseif {$n < 65536} {
565 return [format "y%.4x" $n]
567 return [format "z%.8x" $n]
570 # Procedures used in reordering commits from git log (without
571 # --topo-order) into the order for display.
573 proc varcinit {view} {
574 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
575 global vtokmod varcmod vrowmod varcix vlastins
577 set varcstart($view) {{}}
578 set vupptr($view) {0}
579 set vdownptr($view) {0}
580 set vleftptr($view) {0}
581 set vbackptr($view) {0}
582 set varctok($view) {{}}
583 set varcrow($view) {{}}
584 set vtokmod($view) {}
585 set varcmod($view) 0
586 set vrowmod($view) 0
587 set varcix($view) {{}}
588 set vlastins($view) {0}
591 proc resetvarcs {view} {
592 global varcid varccommits parents children vseedcount ordertok
594 foreach vid [array names varcid $view,*] {
595 unset varcid($vid)
596 unset children($vid)
597 unset parents($vid)
599 # some commits might have children but haven't been seen yet
600 foreach vid [array names children $view,*] {
601 unset children($vid)
603 foreach va [array names varccommits $view,*] {
604 unset varccommits($va)
606 foreach vd [array names vseedcount $view,*] {
607 unset vseedcount($vd)
609 catch {unset ordertok}
612 # returns a list of the commits with no children
613 proc seeds {v} {
614 global vdownptr vleftptr varcstart
616 set ret {}
617 set a [lindex $vdownptr($v) 0]
618 while {$a != 0} {
619 lappend ret [lindex $varcstart($v) $a]
620 set a [lindex $vleftptr($v) $a]
622 return $ret
625 proc newvarc {view id} {
626 global varcid varctok parents children vdatemode
627 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
628 global commitdata commitinfo vseedcount varccommits vlastins
630 set a [llength $varctok($view)]
631 set vid $view,$id
632 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
633 if {![info exists commitinfo($id)]} {
634 parsecommit $id $commitdata($id) 1
636 set cdate [lindex $commitinfo($id) 4]
637 if {![string is integer -strict $cdate]} {
638 set cdate 0
640 if {![info exists vseedcount($view,$cdate)]} {
641 set vseedcount($view,$cdate) -1
643 set c [incr vseedcount($view,$cdate)]
644 set cdate [expr {$cdate ^ 0xffffffff}]
645 set tok "s[strrep $cdate][strrep $c]"
646 } else {
647 set tok {}
649 set ka 0
650 if {[llength $children($vid)] > 0} {
651 set kid [lindex $children($vid) end]
652 set k $varcid($view,$kid)
653 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
654 set ki $kid
655 set ka $k
656 set tok [lindex $varctok($view) $k]
659 if {$ka != 0} {
660 set i [lsearch -exact $parents($view,$ki) $id]
661 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
662 append tok [strrep $j]
664 set c [lindex $vlastins($view) $ka]
665 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
666 set c $ka
667 set b [lindex $vdownptr($view) $ka]
668 } else {
669 set b [lindex $vleftptr($view) $c]
671 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
672 set c $b
673 set b [lindex $vleftptr($view) $c]
675 if {$c == $ka} {
676 lset vdownptr($view) $ka $a
677 lappend vbackptr($view) 0
678 } else {
679 lset vleftptr($view) $c $a
680 lappend vbackptr($view) $c
682 lset vlastins($view) $ka $a
683 lappend vupptr($view) $ka
684 lappend vleftptr($view) $b
685 if {$b != 0} {
686 lset vbackptr($view) $b $a
688 lappend varctok($view) $tok
689 lappend varcstart($view) $id
690 lappend vdownptr($view) 0
691 lappend varcrow($view) {}
692 lappend varcix($view) {}
693 set varccommits($view,$a) {}
694 lappend vlastins($view) 0
695 return $a
698 proc splitvarc {p v} {
699 global varcid varcstart varccommits varctok
700 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
702 set oa $varcid($v,$p)
703 set ac $varccommits($v,$oa)
704 set i [lsearch -exact $varccommits($v,$oa) $p]
705 if {$i <= 0} return
706 set na [llength $varctok($v)]
707 # "%" sorts before "0"...
708 set tok "[lindex $varctok($v) $oa]%[strrep $i]"
709 lappend varctok($v) $tok
710 lappend varcrow($v) {}
711 lappend varcix($v) {}
712 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
713 set varccommits($v,$na) [lrange $ac $i end]
714 lappend varcstart($v) $p
715 foreach id $varccommits($v,$na) {
716 set varcid($v,$id) $na
718 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
719 lappend vlastins($v) [lindex $vlastins($v) $oa]
720 lset vdownptr($v) $oa $na
721 lset vlastins($v) $oa 0
722 lappend vupptr($v) $oa
723 lappend vleftptr($v) 0
724 lappend vbackptr($v) 0
725 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
726 lset vupptr($v) $b $na
730 proc renumbervarc {a v} {
731 global parents children varctok varcstart varccommits
732 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
734 set t1 [clock clicks -milliseconds]
735 set todo {}
736 set isrelated($a) 1
737 set kidchanged($a) 1
738 set ntot 0
739 while {$a != 0} {
740 if {[info exists isrelated($a)]} {
741 lappend todo $a
742 set id [lindex $varccommits($v,$a) end]
743 foreach p $parents($v,$id) {
744 if {[info exists varcid($v,$p)]} {
745 set isrelated($varcid($v,$p)) 1
749 incr ntot
750 set b [lindex $vdownptr($v) $a]
751 if {$b == 0} {
752 while {$a != 0} {
753 set b [lindex $vleftptr($v) $a]
754 if {$b != 0} break
755 set a [lindex $vupptr($v) $a]
758 set a $b
760 foreach a $todo {
761 if {![info exists kidchanged($a)]} continue
762 set id [lindex $varcstart($v) $a]
763 if {[llength $children($v,$id)] > 1} {
764 set children($v,$id) [lsort -command [list vtokcmp $v] \
765 $children($v,$id)]
767 set oldtok [lindex $varctok($v) $a]
768 if {!$vdatemode($v)} {
769 set tok {}
770 } else {
771 set tok $oldtok
773 set ka 0
774 set kid [last_real_child $v,$id]
775 if {$kid ne {}} {
776 set k $varcid($v,$kid)
777 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
778 set ki $kid
779 set ka $k
780 set tok [lindex $varctok($v) $k]
783 if {$ka != 0} {
784 set i [lsearch -exact $parents($v,$ki) $id]
785 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
786 append tok [strrep $j]
788 if {$tok eq $oldtok} {
789 continue
791 set id [lindex $varccommits($v,$a) end]
792 foreach p $parents($v,$id) {
793 if {[info exists varcid($v,$p)]} {
794 set kidchanged($varcid($v,$p)) 1
795 } else {
796 set sortkids($p) 1
799 lset varctok($v) $a $tok
800 set b [lindex $vupptr($v) $a]
801 if {$b != $ka} {
802 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
803 modify_arc $v $ka
805 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
806 modify_arc $v $b
808 set c [lindex $vbackptr($v) $a]
809 set d [lindex $vleftptr($v) $a]
810 if {$c == 0} {
811 lset vdownptr($v) $b $d
812 } else {
813 lset vleftptr($v) $c $d
815 if {$d != 0} {
816 lset vbackptr($v) $d $c
818 if {[lindex $vlastins($v) $b] == $a} {
819 lset vlastins($v) $b $c
821 lset vupptr($v) $a $ka
822 set c [lindex $vlastins($v) $ka]
823 if {$c == 0 || \
824 [string compare $tok [lindex $varctok($v) $c]] < 0} {
825 set c $ka
826 set b [lindex $vdownptr($v) $ka]
827 } else {
828 set b [lindex $vleftptr($v) $c]
830 while {$b != 0 && \
831 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
832 set c $b
833 set b [lindex $vleftptr($v) $c]
835 if {$c == $ka} {
836 lset vdownptr($v) $ka $a
837 lset vbackptr($v) $a 0
838 } else {
839 lset vleftptr($v) $c $a
840 lset vbackptr($v) $a $c
842 lset vleftptr($v) $a $b
843 if {$b != 0} {
844 lset vbackptr($v) $b $a
846 lset vlastins($v) $ka $a
849 foreach id [array names sortkids] {
850 if {[llength $children($v,$id)] > 1} {
851 set children($v,$id) [lsort -command [list vtokcmp $v] \
852 $children($v,$id)]
855 set t2 [clock clicks -milliseconds]
856 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
859 # Fix up the graph after we have found out that in view $v,
860 # $p (a commit that we have already seen) is actually the parent
861 # of the last commit in arc $a.
862 proc fix_reversal {p a v} {
863 global varcid varcstart varctok vupptr
865 set pa $varcid($v,$p)
866 if {$p ne [lindex $varcstart($v) $pa]} {
867 splitvarc $p $v
868 set pa $varcid($v,$p)
870 # seeds always need to be renumbered
871 if {[lindex $vupptr($v) $pa] == 0 ||
872 [string compare [lindex $varctok($v) $a] \
873 [lindex $varctok($v) $pa]] > 0} {
874 renumbervarc $pa $v
878 proc insertrow {id p v} {
879 global cmitlisted children parents varcid varctok vtokmod
880 global varccommits ordertok commitidx numcommits curview
881 global targetid targetrow
883 readcommit $id
884 set vid $v,$id
885 set cmitlisted($vid) 1
886 set children($vid) {}
887 set parents($vid) [list $p]
888 set a [newvarc $v $id]
889 set varcid($vid) $a
890 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
891 modify_arc $v $a
893 lappend varccommits($v,$a) $id
894 set vp $v,$p
895 if {[llength [lappend children($vp) $id]] > 1} {
896 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
897 catch {unset ordertok}
899 fix_reversal $p $a $v
900 incr commitidx($v)
901 if {$v == $curview} {
902 set numcommits $commitidx($v)
903 setcanvscroll
904 if {[info exists targetid]} {
905 if {![comes_before $targetid $p]} {
906 incr targetrow
912 proc insertfakerow {id p} {
913 global varcid varccommits parents children cmitlisted
914 global commitidx varctok vtokmod targetid targetrow curview numcommits
916 set v $curview
917 set a $varcid($v,$p)
918 set i [lsearch -exact $varccommits($v,$a) $p]
919 if {$i < 0} {
920 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
921 return
923 set children($v,$id) {}
924 set parents($v,$id) [list $p]
925 set varcid($v,$id) $a
926 lappend children($v,$p) $id
927 set cmitlisted($v,$id) 1
928 set numcommits [incr commitidx($v)]
929 # note we deliberately don't update varcstart($v) even if $i == 0
930 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
931 modify_arc $v $a $i
932 if {[info exists targetid]} {
933 if {![comes_before $targetid $p]} {
934 incr targetrow
937 setcanvscroll
938 drawvisible
941 proc removefakerow {id} {
942 global varcid varccommits parents children commitidx
943 global varctok vtokmod cmitlisted currentid selectedline
944 global targetid curview numcommits
946 set v $curview
947 if {[llength $parents($v,$id)] != 1} {
948 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
949 return
951 set p [lindex $parents($v,$id) 0]
952 set a $varcid($v,$id)
953 set i [lsearch -exact $varccommits($v,$a) $id]
954 if {$i < 0} {
955 puts "oops: removefakerow can't find [shortids $id] on arc $a"
956 return
958 unset varcid($v,$id)
959 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
960 unset parents($v,$id)
961 unset children($v,$id)
962 unset cmitlisted($v,$id)
963 set numcommits [incr commitidx($v) -1]
964 set j [lsearch -exact $children($v,$p) $id]
965 if {$j >= 0} {
966 set children($v,$p) [lreplace $children($v,$p) $j $j]
968 modify_arc $v $a $i
969 if {[info exist currentid] && $id eq $currentid} {
970 unset currentid
971 set selectedline {}
973 if {[info exists targetid] && $targetid eq $id} {
974 set targetid $p
976 setcanvscroll
977 drawvisible
980 proc first_real_child {vp} {
981 global children nullid nullid2
983 foreach id $children($vp) {
984 if {$id ne $nullid && $id ne $nullid2} {
985 return $id
988 return {}
991 proc last_real_child {vp} {
992 global children nullid nullid2
994 set kids $children($vp)
995 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
996 set id [lindex $kids $i]
997 if {$id ne $nullid && $id ne $nullid2} {
998 return $id
1001 return {}
1004 proc vtokcmp {v a b} {
1005 global varctok varcid
1007 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1008 [lindex $varctok($v) $varcid($v,$b)]]
1011 # This assumes that if lim is not given, the caller has checked that
1012 # arc a's token is less than $vtokmod($v)
1013 proc modify_arc {v a {lim {}}} {
1014 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1016 if {$lim ne {}} {
1017 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1018 if {$c > 0} return
1019 if {$c == 0} {
1020 set r [lindex $varcrow($v) $a]
1021 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1024 set vtokmod($v) [lindex $varctok($v) $a]
1025 set varcmod($v) $a
1026 if {$v == $curview} {
1027 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1028 set a [lindex $vupptr($v) $a]
1029 set lim {}
1031 set r 0
1032 if {$a != 0} {
1033 if {$lim eq {}} {
1034 set lim [llength $varccommits($v,$a)]
1036 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1038 set vrowmod($v) $r
1039 undolayout $r
1043 proc update_arcrows {v} {
1044 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1045 global varcid vrownum varcorder varcix varccommits
1046 global vupptr vdownptr vleftptr varctok
1047 global displayorder parentlist curview cached_commitrow
1049 if {$vrowmod($v) == $commitidx($v)} return
1050 if {$v == $curview} {
1051 if {[llength $displayorder] > $vrowmod($v)} {
1052 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1053 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1055 catch {unset cached_commitrow}
1057 set narctot [expr {[llength $varctok($v)] - 1}]
1058 set a $varcmod($v)
1059 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1060 # go up the tree until we find something that has a row number,
1061 # or we get to a seed
1062 set a [lindex $vupptr($v) $a]
1064 if {$a == 0} {
1065 set a [lindex $vdownptr($v) 0]
1066 if {$a == 0} return
1067 set vrownum($v) {0}
1068 set varcorder($v) [list $a]
1069 lset varcix($v) $a 0
1070 lset varcrow($v) $a 0
1071 set arcn 0
1072 set row 0
1073 } else {
1074 set arcn [lindex $varcix($v) $a]
1075 if {[llength $vrownum($v)] > $arcn + 1} {
1076 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1077 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1079 set row [lindex $varcrow($v) $a]
1081 while {1} {
1082 set p $a
1083 incr row [llength $varccommits($v,$a)]
1084 # go down if possible
1085 set b [lindex $vdownptr($v) $a]
1086 if {$b == 0} {
1087 # if not, go left, or go up until we can go left
1088 while {$a != 0} {
1089 set b [lindex $vleftptr($v) $a]
1090 if {$b != 0} break
1091 set a [lindex $vupptr($v) $a]
1093 if {$a == 0} break
1095 set a $b
1096 incr arcn
1097 lappend vrownum($v) $row
1098 lappend varcorder($v) $a
1099 lset varcix($v) $a $arcn
1100 lset varcrow($v) $a $row
1102 set vtokmod($v) [lindex $varctok($v) $p]
1103 set varcmod($v) $p
1104 set vrowmod($v) $row
1105 if {[info exists currentid]} {
1106 set selectedline [rowofcommit $currentid]
1110 # Test whether view $v contains commit $id
1111 proc commitinview {id v} {
1112 global varcid
1114 return [info exists varcid($v,$id)]
1117 # Return the row number for commit $id in the current view
1118 proc rowofcommit {id} {
1119 global varcid varccommits varcrow curview cached_commitrow
1120 global varctok vtokmod
1122 set v $curview
1123 if {![info exists varcid($v,$id)]} {
1124 puts "oops rowofcommit no arc for [shortids $id]"
1125 return {}
1127 set a $varcid($v,$id)
1128 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1129 update_arcrows $v
1131 if {[info exists cached_commitrow($id)]} {
1132 return $cached_commitrow($id)
1134 set i [lsearch -exact $varccommits($v,$a) $id]
1135 if {$i < 0} {
1136 puts "oops didn't find commit [shortids $id] in arc $a"
1137 return {}
1139 incr i [lindex $varcrow($v) $a]
1140 set cached_commitrow($id) $i
1141 return $i
1144 # Returns 1 if a is on an earlier row than b, otherwise 0
1145 proc comes_before {a b} {
1146 global varcid varctok curview
1148 set v $curview
1149 if {$a eq $b || ![info exists varcid($v,$a)] || \
1150 ![info exists varcid($v,$b)]} {
1151 return 0
1153 if {$varcid($v,$a) != $varcid($v,$b)} {
1154 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1155 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1157 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1160 proc bsearch {l elt} {
1161 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1162 return 0
1164 set lo 0
1165 set hi [llength $l]
1166 while {$hi - $lo > 1} {
1167 set mid [expr {int(($lo + $hi) / 2)}]
1168 set t [lindex $l $mid]
1169 if {$elt < $t} {
1170 set hi $mid
1171 } elseif {$elt > $t} {
1172 set lo $mid
1173 } else {
1174 return $mid
1177 return $lo
1180 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1181 proc make_disporder {start end} {
1182 global vrownum curview commitidx displayorder parentlist
1183 global varccommits varcorder parents vrowmod varcrow
1184 global d_valid_start d_valid_end
1186 if {$end > $vrowmod($curview)} {
1187 update_arcrows $curview
1189 set ai [bsearch $vrownum($curview) $start]
1190 set start [lindex $vrownum($curview) $ai]
1191 set narc [llength $vrownum($curview)]
1192 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1193 set a [lindex $varcorder($curview) $ai]
1194 set l [llength $displayorder]
1195 set al [llength $varccommits($curview,$a)]
1196 if {$l < $r + $al} {
1197 if {$l < $r} {
1198 set pad [ntimes [expr {$r - $l}] {}]
1199 set displayorder [concat $displayorder $pad]
1200 set parentlist [concat $parentlist $pad]
1201 } elseif {$l > $r} {
1202 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1203 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1205 foreach id $varccommits($curview,$a) {
1206 lappend displayorder $id
1207 lappend parentlist $parents($curview,$id)
1209 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1210 set i $r
1211 foreach id $varccommits($curview,$a) {
1212 lset displayorder $i $id
1213 lset parentlist $i $parents($curview,$id)
1214 incr i
1217 incr r $al
1221 proc commitonrow {row} {
1222 global displayorder
1224 set id [lindex $displayorder $row]
1225 if {$id eq {}} {
1226 make_disporder $row [expr {$row + 1}]
1227 set id [lindex $displayorder $row]
1229 return $id
1232 proc closevarcs {v} {
1233 global varctok varccommits varcid parents children
1234 global cmitlisted commitidx vtokmod
1236 set missing_parents 0
1237 set scripts {}
1238 set narcs [llength $varctok($v)]
1239 for {set a 1} {$a < $narcs} {incr a} {
1240 set id [lindex $varccommits($v,$a) end]
1241 foreach p $parents($v,$id) {
1242 if {[info exists varcid($v,$p)]} continue
1243 # add p as a new commit
1244 incr missing_parents
1245 set cmitlisted($v,$p) 0
1246 set parents($v,$p) {}
1247 if {[llength $children($v,$p)] == 1 &&
1248 [llength $parents($v,$id)] == 1} {
1249 set b $a
1250 } else {
1251 set b [newvarc $v $p]
1253 set varcid($v,$p) $b
1254 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1255 modify_arc $v $b
1257 lappend varccommits($v,$b) $p
1258 incr commitidx($v)
1259 set scripts [check_interest $p $scripts]
1262 if {$missing_parents > 0} {
1263 foreach s $scripts {
1264 eval $s
1269 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1270 # Assumes we already have an arc for $rwid.
1271 proc rewrite_commit {v id rwid} {
1272 global children parents varcid varctok vtokmod varccommits
1274 foreach ch $children($v,$id) {
1275 # make $rwid be $ch's parent in place of $id
1276 set i [lsearch -exact $parents($v,$ch) $id]
1277 if {$i < 0} {
1278 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1280 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1281 # add $ch to $rwid's children and sort the list if necessary
1282 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1283 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1284 $children($v,$rwid)]
1286 # fix the graph after joining $id to $rwid
1287 set a $varcid($v,$ch)
1288 fix_reversal $rwid $a $v
1289 # parentlist is wrong for the last element of arc $a
1290 # even if displayorder is right, hence the 3rd arg here
1291 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1295 # Mechanism for registering a command to be executed when we come
1296 # across a particular commit. To handle the case when only the
1297 # prefix of the commit is known, the commitinterest array is now
1298 # indexed by the first 4 characters of the ID. Each element is a
1299 # list of id, cmd pairs.
1300 proc interestedin {id cmd} {
1301 global commitinterest
1303 lappend commitinterest([string range $id 0 3]) $id $cmd
1306 proc check_interest {id scripts} {
1307 global commitinterest
1309 set prefix [string range $id 0 3]
1310 if {[info exists commitinterest($prefix)]} {
1311 set newlist {}
1312 foreach {i script} $commitinterest($prefix) {
1313 if {[string match "$i*" $id]} {
1314 lappend scripts [string map [list "%I" $id "%P" $i] $script]
1315 } else {
1316 lappend newlist $i $script
1319 if {$newlist ne {}} {
1320 set commitinterest($prefix) $newlist
1321 } else {
1322 unset commitinterest($prefix)
1325 return $scripts
1328 proc getcommitlines {fd inst view updating} {
1329 global cmitlisted leftover
1330 global commitidx commitdata vdatemode
1331 global parents children curview hlview
1332 global idpending ordertok
1333 global varccommits varcid varctok vtokmod vfilelimit
1335 set stuff [read $fd 500000]
1336 # git log doesn't terminate the last commit with a null...
1337 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1338 set stuff "\0"
1340 if {$stuff == {}} {
1341 if {![eof $fd]} {
1342 return 1
1344 global commfd viewcomplete viewactive viewname
1345 global viewinstances
1346 unset commfd($inst)
1347 set i [lsearch -exact $viewinstances($view) $inst]
1348 if {$i >= 0} {
1349 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1351 # set it blocking so we wait for the process to terminate
1352 fconfigure $fd -blocking 1
1353 if {[catch {close $fd} err]} {
1354 set fv {}
1355 if {$view != $curview} {
1356 set fv " for the \"$viewname($view)\" view"
1358 if {[string range $err 0 4] == "usage"} {
1359 set err "Gitk: error reading commits$fv:\
1360 bad arguments to git log."
1361 if {$viewname($view) eq "Command line"} {
1362 append err \
1363 " (Note: arguments to gitk are passed to git log\
1364 to allow selection of commits to be displayed.)"
1366 } else {
1367 set err "Error reading commits$fv: $err"
1369 error_popup $err
1371 if {[incr viewactive($view) -1] <= 0} {
1372 set viewcomplete($view) 1
1373 # Check if we have seen any ids listed as parents that haven't
1374 # appeared in the list
1375 closevarcs $view
1376 notbusy $view
1378 if {$view == $curview} {
1379 run chewcommits
1381 return 0
1383 set start 0
1384 set gotsome 0
1385 set scripts {}
1386 while 1 {
1387 set i [string first "\0" $stuff $start]
1388 if {$i < 0} {
1389 append leftover($inst) [string range $stuff $start end]
1390 break
1392 if {$start == 0} {
1393 set cmit $leftover($inst)
1394 append cmit [string range $stuff 0 [expr {$i - 1}]]
1395 set leftover($inst) {}
1396 } else {
1397 set cmit [string range $stuff $start [expr {$i - 1}]]
1399 set start [expr {$i + 1}]
1400 set j [string first "\n" $cmit]
1401 set ok 0
1402 set listed 1
1403 if {$j >= 0 && [string match "commit *" $cmit]} {
1404 set ids [string range $cmit 7 [expr {$j - 1}]]
1405 if {[string match {[-^<>]*} $ids]} {
1406 switch -- [string index $ids 0] {
1407 "-" {set listed 0}
1408 "^" {set listed 2}
1409 "<" {set listed 3}
1410 ">" {set listed 4}
1412 set ids [string range $ids 1 end]
1414 set ok 1
1415 foreach id $ids {
1416 if {[string length $id] != 40} {
1417 set ok 0
1418 break
1422 if {!$ok} {
1423 set shortcmit $cmit
1424 if {[string length $shortcmit] > 80} {
1425 set shortcmit "[string range $shortcmit 0 80]..."
1427 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1428 exit 1
1430 set id [lindex $ids 0]
1431 set vid $view,$id
1433 if {!$listed && $updating && ![info exists varcid($vid)] &&
1434 $vfilelimit($view) ne {}} {
1435 # git log doesn't rewrite parents for unlisted commits
1436 # when doing path limiting, so work around that here
1437 # by working out the rewritten parent with git rev-list
1438 # and if we already know about it, using the rewritten
1439 # parent as a substitute parent for $id's children.
1440 if {![catch {
1441 set rwid [exec git rev-list --first-parent --max-count=1 \
1442 $id -- $vfilelimit($view)]
1443 }]} {
1444 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1445 # use $rwid in place of $id
1446 rewrite_commit $view $id $rwid
1447 continue
1452 set a 0
1453 if {[info exists varcid($vid)]} {
1454 if {$cmitlisted($vid) || !$listed} continue
1455 set a $varcid($vid)
1457 if {$listed} {
1458 set olds [lrange $ids 1 end]
1459 } else {
1460 set olds {}
1462 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1463 set cmitlisted($vid) $listed
1464 set parents($vid) $olds
1465 if {![info exists children($vid)]} {
1466 set children($vid) {}
1467 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1468 set k [lindex $children($vid) 0]
1469 if {[llength $parents($view,$k)] == 1 &&
1470 (!$vdatemode($view) ||
1471 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1472 set a $varcid($view,$k)
1475 if {$a == 0} {
1476 # new arc
1477 set a [newvarc $view $id]
1479 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1480 modify_arc $view $a
1482 if {![info exists varcid($vid)]} {
1483 set varcid($vid) $a
1484 lappend varccommits($view,$a) $id
1485 incr commitidx($view)
1488 set i 0
1489 foreach p $olds {
1490 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1491 set vp $view,$p
1492 if {[llength [lappend children($vp) $id]] > 1 &&
1493 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1494 set children($vp) [lsort -command [list vtokcmp $view] \
1495 $children($vp)]
1496 catch {unset ordertok}
1498 if {[info exists varcid($view,$p)]} {
1499 fix_reversal $p $a $view
1502 incr i
1505 set scripts [check_interest $id $scripts]
1506 set gotsome 1
1508 if {$gotsome} {
1509 global numcommits hlview
1511 if {$view == $curview} {
1512 set numcommits $commitidx($view)
1513 run chewcommits
1515 if {[info exists hlview] && $view == $hlview} {
1516 # we never actually get here...
1517 run vhighlightmore
1519 foreach s $scripts {
1520 eval $s
1523 return 2
1526 proc chewcommits {} {
1527 global curview hlview viewcomplete
1528 global pending_select
1530 layoutmore
1531 if {$viewcomplete($curview)} {
1532 global commitidx varctok
1533 global numcommits startmsecs
1535 if {[info exists pending_select]} {
1536 update
1537 reset_pending_select {}
1539 if {[commitinview $pending_select $curview]} {
1540 selectline [rowofcommit $pending_select] 1
1541 } else {
1542 set row [first_real_row]
1543 selectline $row 1
1546 if {$commitidx($curview) > 0} {
1547 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1548 #puts "overall $ms ms for $numcommits commits"
1549 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1550 } else {
1551 show_status [mc "No commits selected"]
1553 notbusy layout
1555 return 0
1558 proc readcommit {id} {
1559 if {[catch {set contents [exec git cat-file commit $id]}]} return
1560 parsecommit $id $contents 0
1563 proc parsecommit {id contents listed} {
1564 global commitinfo cdate
1566 set inhdr 1
1567 set comment {}
1568 set headline {}
1569 set auname {}
1570 set audate {}
1571 set comname {}
1572 set comdate {}
1573 set hdrend [string first "\n\n" $contents]
1574 if {$hdrend < 0} {
1575 # should never happen...
1576 set hdrend [string length $contents]
1578 set header [string range $contents 0 [expr {$hdrend - 1}]]
1579 set comment [string range $contents [expr {$hdrend + 2}] end]
1580 foreach line [split $header "\n"] {
1581 set tag [lindex $line 0]
1582 if {$tag == "author"} {
1583 set audate [lindex $line end-1]
1584 set auname [lrange $line 1 end-2]
1585 } elseif {$tag == "committer"} {
1586 set comdate [lindex $line end-1]
1587 set comname [lrange $line 1 end-2]
1590 set headline {}
1591 # take the first non-blank line of the comment as the headline
1592 set headline [string trimleft $comment]
1593 set i [string first "\n" $headline]
1594 if {$i >= 0} {
1595 set headline [string range $headline 0 $i]
1597 set headline [string trimright $headline]
1598 set i [string first "\r" $headline]
1599 if {$i >= 0} {
1600 set headline [string trimright [string range $headline 0 $i]]
1602 if {!$listed} {
1603 # git log indents the comment by 4 spaces;
1604 # if we got this via git cat-file, add the indentation
1605 set newcomment {}
1606 foreach line [split $comment "\n"] {
1607 append newcomment " "
1608 append newcomment $line
1609 append newcomment "\n"
1611 set comment $newcomment
1613 if {$comdate != {}} {
1614 set cdate($id) $comdate
1616 set commitinfo($id) [list $headline $auname $audate \
1617 $comname $comdate $comment]
1620 proc getcommit {id} {
1621 global commitdata commitinfo
1623 if {[info exists commitdata($id)]} {
1624 parsecommit $id $commitdata($id) 1
1625 } else {
1626 readcommit $id
1627 if {![info exists commitinfo($id)]} {
1628 set commitinfo($id) [list [mc "No commit information available"]]
1631 return 1
1634 # Expand an abbreviated commit ID to a list of full 40-char IDs that match
1635 # and are present in the current view.
1636 # This is fairly slow...
1637 proc longid {prefix} {
1638 global varcid curview
1640 set ids {}
1641 foreach match [array names varcid "$curview,$prefix*"] {
1642 lappend ids [lindex [split $match ","] 1]
1644 return $ids
1647 proc readrefs {} {
1648 global tagids idtags headids idheads tagobjid
1649 global otherrefids idotherrefs mainhead mainheadid
1650 global selecthead selectheadid
1652 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1653 catch {unset $v}
1655 set refd [open [list | git show-ref -d] r]
1656 while {[gets $refd line] >= 0} {
1657 if {[string index $line 40] ne " "} continue
1658 set id [string range $line 0 39]
1659 set ref [string range $line 41 end]
1660 if {![string match "refs/*" $ref]} continue
1661 set name [string range $ref 5 end]
1662 if {[string match "remotes/*" $name]} {
1663 if {![string match "*/HEAD" $name]} {
1664 set headids($name) $id
1665 lappend idheads($id) $name
1667 } elseif {[string match "heads/*" $name]} {
1668 set name [string range $name 6 end]
1669 set headids($name) $id
1670 lappend idheads($id) $name
1671 } elseif {[string match "tags/*" $name]} {
1672 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1673 # which is what we want since the former is the commit ID
1674 set name [string range $name 5 end]
1675 if {[string match "*^{}" $name]} {
1676 set name [string range $name 0 end-3]
1677 } else {
1678 set tagobjid($name) $id
1680 set tagids($name) $id
1681 lappend idtags($id) $name
1682 } else {
1683 set otherrefids($name) $id
1684 lappend idotherrefs($id) $name
1687 catch {close $refd}
1688 set mainhead {}
1689 set mainheadid {}
1690 catch {
1691 set mainheadid [exec git rev-parse HEAD]
1692 set thehead [exec git symbolic-ref HEAD]
1693 if {[string match "refs/heads/*" $thehead]} {
1694 set mainhead [string range $thehead 11 end]
1697 set selectheadid {}
1698 if {$selecthead ne {}} {
1699 catch {
1700 set selectheadid [exec git rev-parse --verify $selecthead]
1705 # skip over fake commits
1706 proc first_real_row {} {
1707 global nullid nullid2 numcommits
1709 for {set row 0} {$row < $numcommits} {incr row} {
1710 set id [commitonrow $row]
1711 if {$id ne $nullid && $id ne $nullid2} {
1712 break
1715 return $row
1718 # update things for a head moved to a child of its previous location
1719 proc movehead {id name} {
1720 global headids idheads
1722 removehead $headids($name) $name
1723 set headids($name) $id
1724 lappend idheads($id) $name
1727 # update things when a head has been removed
1728 proc removehead {id name} {
1729 global headids idheads
1731 if {$idheads($id) eq $name} {
1732 unset idheads($id)
1733 } else {
1734 set i [lsearch -exact $idheads($id) $name]
1735 if {$i >= 0} {
1736 set idheads($id) [lreplace $idheads($id) $i $i]
1739 unset headids($name)
1742 proc show_error {w top msg} {
1743 message $w.m -text $msg -justify center -aspect 400
1744 pack $w.m -side top -fill x -padx 20 -pady 20
1745 button $w.ok -text [mc OK] -command "destroy $top"
1746 pack $w.ok -side bottom -fill x
1747 bind $top <Visibility> "grab $top; focus $top"
1748 bind $top <Key-Return> "destroy $top"
1749 tkwait window $top
1752 proc error_popup msg {
1753 set w .error
1754 toplevel $w
1755 wm transient $w .
1756 show_error $w $w $msg
1759 proc confirm_popup msg {
1760 global confirm_ok
1761 set confirm_ok 0
1762 set w .confirm
1763 toplevel $w
1764 wm transient $w .
1765 message $w.m -text $msg -justify center -aspect 400
1766 pack $w.m -side top -fill x -padx 20 -pady 20
1767 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1768 pack $w.ok -side left -fill x
1769 button $w.cancel -text [mc Cancel] -command "destroy $w"
1770 pack $w.cancel -side right -fill x
1771 bind $w <Visibility> "grab $w; focus $w"
1772 tkwait window $w
1773 return $confirm_ok
1776 proc setoptions {} {
1777 option add *Panedwindow.showHandle 1 startupFile
1778 option add *Panedwindow.sashRelief raised startupFile
1779 option add *Button.font uifont startupFile
1780 option add *Checkbutton.font uifont startupFile
1781 option add *Radiobutton.font uifont startupFile
1782 option add *Menu.font uifont startupFile
1783 option add *Menubutton.font uifont startupFile
1784 option add *Label.font uifont startupFile
1785 option add *Message.font uifont startupFile
1786 option add *Entry.font uifont startupFile
1789 # Make a menu and submenus.
1790 # m is the window name for the menu, items is the list of menu items to add.
1791 # Each item is a list {mc label type description options...}
1792 # mc is ignored; it's so we can put mc there to alert xgettext
1793 # label is the string that appears in the menu
1794 # type is cascade, command or radiobutton (should add checkbutton)
1795 # description depends on type; it's the sublist for cascade, the
1796 # command to invoke for command, or {variable value} for radiobutton
1797 proc makemenu {m items} {
1798 menu $m
1799 foreach i $items {
1800 set name [mc [lindex $i 1]]
1801 set type [lindex $i 2]
1802 set thing [lindex $i 3]
1803 set params [list $type]
1804 if {$name ne {}} {
1805 set u [string first "&" [string map {&& x} $name]]
1806 lappend params -label [string map {&& & & {}} $name]
1807 if {$u >= 0} {
1808 lappend params -underline $u
1811 switch -- $type {
1812 "cascade" {
1813 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1814 lappend params -menu $m.$submenu
1816 "command" {
1817 lappend params -command $thing
1819 "radiobutton" {
1820 lappend params -variable [lindex $thing 0] \
1821 -value [lindex $thing 1]
1824 eval $m add $params [lrange $i 4 end]
1825 if {$type eq "cascade"} {
1826 makemenu $m.$submenu $thing
1831 # translate string and remove ampersands
1832 proc mca {str} {
1833 return [string map {&& & & {}} [mc $str]]
1836 proc makewindow {} {
1837 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1838 global tabstop
1839 global findtype findtypemenu findloc findstring fstring geometry
1840 global entries sha1entry sha1string sha1but
1841 global diffcontextstring diffcontext
1842 global ignorespace
1843 global maincursor textcursor curtextcursor
1844 global rowctxmenu fakerowmenu mergemax wrapcomment
1845 global highlight_files gdttype
1846 global searchstring sstring
1847 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1848 global headctxmenu progresscanv progressitem progresscoords statusw
1849 global fprogitem fprogcoord lastprogupdate progupdatepending
1850 global rprogitem rprogcoord rownumsel numcommits
1851 global have_tk85
1853 # The "mc" arguments here are purely so that xgettext
1854 # sees the following string as needing to be translated
1855 makemenu .bar {
1856 {mc "File" cascade {
1857 {mc "Update" command updatecommits -accelerator F5}
1858 {mc "Reload" command reloadcommits}
1859 {mc "Reread references" command rereadrefs}
1860 {mc "List references" command showrefs}
1861 {mc "Quit" command doquit}
1863 {mc "Edit" cascade {
1864 {mc "Preferences" command doprefs}
1866 {mc "View" cascade {
1867 {mc "New view..." command {newview 0}}
1868 {mc "Edit view..." command editview -state disabled}
1869 {mc "Delete view" command delview -state disabled}
1870 {xx "" separator}
1871 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
1873 {mc "Help" cascade {
1874 {mc "About gitk" command about}
1875 {mc "Key bindings" command keys}
1878 . configure -menu .bar
1880 # the gui has upper and lower half, parts of a paned window.
1881 panedwindow .ctop -orient vertical
1883 # possibly use assumed geometry
1884 if {![info exists geometry(pwsash0)]} {
1885 set geometry(topheight) [expr {15 * $linespc}]
1886 set geometry(topwidth) [expr {80 * $charspc}]
1887 set geometry(botheight) [expr {15 * $linespc}]
1888 set geometry(botwidth) [expr {50 * $charspc}]
1889 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1890 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1893 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1894 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1895 frame .tf.histframe
1896 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1898 # create three canvases
1899 set cscroll .tf.histframe.csb
1900 set canv .tf.histframe.pwclist.canv
1901 canvas $canv \
1902 -selectbackground $selectbgcolor \
1903 -background $bgcolor -bd 0 \
1904 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1905 .tf.histframe.pwclist add $canv
1906 set canv2 .tf.histframe.pwclist.canv2
1907 canvas $canv2 \
1908 -selectbackground $selectbgcolor \
1909 -background $bgcolor -bd 0 -yscrollincr $linespc
1910 .tf.histframe.pwclist add $canv2
1911 set canv3 .tf.histframe.pwclist.canv3
1912 canvas $canv3 \
1913 -selectbackground $selectbgcolor \
1914 -background $bgcolor -bd 0 -yscrollincr $linespc
1915 .tf.histframe.pwclist add $canv3
1916 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1917 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1919 # a scroll bar to rule them
1920 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1921 pack $cscroll -side right -fill y
1922 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1923 lappend bglist $canv $canv2 $canv3
1924 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1926 # we have two button bars at bottom of top frame. Bar 1
1927 frame .tf.bar
1928 frame .tf.lbar -height 15
1930 set sha1entry .tf.bar.sha1
1931 set entries $sha1entry
1932 set sha1but .tf.bar.sha1label
1933 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1934 -command gotocommit -width 8
1935 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1936 pack .tf.bar.sha1label -side left
1937 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1938 trace add variable sha1string write sha1change
1939 pack $sha1entry -side left -pady 2
1941 image create bitmap bm-left -data {
1942 #define left_width 16
1943 #define left_height 16
1944 static unsigned char left_bits[] = {
1945 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1946 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1947 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1949 image create bitmap bm-right -data {
1950 #define right_width 16
1951 #define right_height 16
1952 static unsigned char right_bits[] = {
1953 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1954 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1955 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1957 button .tf.bar.leftbut -image bm-left -command goback \
1958 -state disabled -width 26
1959 pack .tf.bar.leftbut -side left -fill y
1960 button .tf.bar.rightbut -image bm-right -command goforw \
1961 -state disabled -width 26
1962 pack .tf.bar.rightbut -side left -fill y
1964 label .tf.bar.rowlabel -text [mc "Row"]
1965 set rownumsel {}
1966 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
1967 -relief sunken -anchor e
1968 label .tf.bar.rowlabel2 -text "/"
1969 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
1970 -relief sunken -anchor e
1971 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
1972 -side left
1973 global selectedline
1974 trace add variable selectedline write selectedline_change
1976 # Status label and progress bar
1977 set statusw .tf.bar.status
1978 label $statusw -width 15 -relief sunken
1979 pack $statusw -side left -padx 5
1980 set h [expr {[font metrics uifont -linespace] + 2}]
1981 set progresscanv .tf.bar.progress
1982 canvas $progresscanv -relief sunken -height $h -borderwidth 2
1983 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1984 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1985 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1986 pack $progresscanv -side right -expand 1 -fill x
1987 set progresscoords {0 0}
1988 set fprogcoord 0
1989 set rprogcoord 0
1990 bind $progresscanv <Configure> adjustprogress
1991 set lastprogupdate [clock clicks -milliseconds]
1992 set progupdatepending 0
1994 # build up the bottom bar of upper window
1995 label .tf.lbar.flabel -text "[mc "Find"] "
1996 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
1997 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
1998 label .tf.lbar.flab2 -text " [mc "commit"] "
1999 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2000 -side left -fill y
2001 set gdttype [mc "containing:"]
2002 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
2003 [mc "containing:"] \
2004 [mc "touching paths:"] \
2005 [mc "adding/removing string:"]]
2006 trace add variable gdttype write gdttype_change
2007 pack .tf.lbar.gdttype -side left -fill y
2009 set findstring {}
2010 set fstring .tf.lbar.findstring
2011 lappend entries $fstring
2012 entry $fstring -width 30 -font textfont -textvariable findstring
2013 trace add variable findstring write find_change
2014 set findtype [mc "Exact"]
2015 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
2016 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2017 trace add variable findtype write findcom_change
2018 set findloc [mc "All fields"]
2019 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2020 [mc "Comments"] [mc "Author"] [mc "Committer"]
2021 trace add variable findloc write find_change
2022 pack .tf.lbar.findloc -side right
2023 pack .tf.lbar.findtype -side right
2024 pack $fstring -side left -expand 1 -fill x
2026 # Finish putting the upper half of the viewer together
2027 pack .tf.lbar -in .tf -side bottom -fill x
2028 pack .tf.bar -in .tf -side bottom -fill x
2029 pack .tf.histframe -fill both -side top -expand 1
2030 .ctop add .tf
2031 .ctop paneconfigure .tf -height $geometry(topheight)
2032 .ctop paneconfigure .tf -width $geometry(topwidth)
2034 # now build up the bottom
2035 panedwindow .pwbottom -orient horizontal
2037 # lower left, a text box over search bar, scroll bar to the right
2038 # if we know window height, then that will set the lower text height, otherwise
2039 # we set lower text height which will drive window height
2040 if {[info exists geometry(main)]} {
2041 frame .bleft -width $geometry(botwidth)
2042 } else {
2043 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2045 frame .bleft.top
2046 frame .bleft.mid
2047 frame .bleft.bottom
2049 button .bleft.top.search -text [mc "Search"] -command dosearch
2050 pack .bleft.top.search -side left -padx 5
2051 set sstring .bleft.top.sstring
2052 entry $sstring -width 20 -font textfont -textvariable searchstring
2053 lappend entries $sstring
2054 trace add variable searchstring write incrsearch
2055 pack $sstring -side left -expand 1 -fill x
2056 radiobutton .bleft.mid.diff -text [mc "Diff"] \
2057 -command changediffdisp -variable diffelide -value {0 0}
2058 radiobutton .bleft.mid.old -text [mc "Old version"] \
2059 -command changediffdisp -variable diffelide -value {0 1}
2060 radiobutton .bleft.mid.new -text [mc "New version"] \
2061 -command changediffdisp -variable diffelide -value {1 0}
2062 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2063 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2064 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
2065 -from 1 -increment 1 -to 10000000 \
2066 -validate all -validatecommand "diffcontextvalidate %P" \
2067 -textvariable diffcontextstring
2068 .bleft.mid.diffcontext set $diffcontext
2069 trace add variable diffcontextstring write diffcontextchange
2070 lappend entries .bleft.mid.diffcontext
2071 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2072 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2073 -command changeignorespace -variable ignorespace
2074 pack .bleft.mid.ignspace -side left -padx 5
2075 set ctext .bleft.bottom.ctext
2076 text $ctext -background $bgcolor -foreground $fgcolor \
2077 -state disabled -font textfont \
2078 -yscrollcommand scrolltext -wrap none \
2079 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2080 if {$have_tk85} {
2081 $ctext conf -tabstyle wordprocessor
2083 scrollbar .bleft.bottom.sb -command "$ctext yview"
2084 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
2085 -width 10
2086 pack .bleft.top -side top -fill x
2087 pack .bleft.mid -side top -fill x
2088 grid $ctext .bleft.bottom.sb -sticky nsew
2089 grid .bleft.bottom.sbhorizontal -sticky ew
2090 grid columnconfigure .bleft.bottom 0 -weight 1
2091 grid rowconfigure .bleft.bottom 0 -weight 1
2092 grid rowconfigure .bleft.bottom 1 -weight 0
2093 pack .bleft.bottom -side top -fill both -expand 1
2094 lappend bglist $ctext
2095 lappend fglist $ctext
2097 $ctext tag conf comment -wrap $wrapcomment
2098 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2099 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2100 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2101 $ctext tag conf d1 -fore [lindex $diffcolors 1]
2102 $ctext tag conf m0 -fore red
2103 $ctext tag conf m1 -fore blue
2104 $ctext tag conf m2 -fore green
2105 $ctext tag conf m3 -fore purple
2106 $ctext tag conf m4 -fore brown
2107 $ctext tag conf m5 -fore "#009090"
2108 $ctext tag conf m6 -fore magenta
2109 $ctext tag conf m7 -fore "#808000"
2110 $ctext tag conf m8 -fore "#009000"
2111 $ctext tag conf m9 -fore "#ff0080"
2112 $ctext tag conf m10 -fore cyan
2113 $ctext tag conf m11 -fore "#b07070"
2114 $ctext tag conf m12 -fore "#70b0f0"
2115 $ctext tag conf m13 -fore "#70f0b0"
2116 $ctext tag conf m14 -fore "#f0b070"
2117 $ctext tag conf m15 -fore "#ff70b0"
2118 $ctext tag conf mmax -fore darkgrey
2119 set mergemax 16
2120 $ctext tag conf mresult -font textfontbold
2121 $ctext tag conf msep -font textfontbold
2122 $ctext tag conf found -back yellow
2124 .pwbottom add .bleft
2125 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2127 # lower right
2128 frame .bright
2129 frame .bright.mode
2130 radiobutton .bright.mode.patch -text [mc "Patch"] \
2131 -command reselectline -variable cmitmode -value "patch"
2132 radiobutton .bright.mode.tree -text [mc "Tree"] \
2133 -command reselectline -variable cmitmode -value "tree"
2134 grid .bright.mode.patch .bright.mode.tree -sticky ew
2135 pack .bright.mode -side top -fill x
2136 set cflist .bright.cfiles
2137 set indent [font measure mainfont "nn"]
2138 text $cflist \
2139 -selectbackground $selectbgcolor \
2140 -background $bgcolor -foreground $fgcolor \
2141 -font mainfont \
2142 -tabs [list $indent [expr {2 * $indent}]] \
2143 -yscrollcommand ".bright.sb set" \
2144 -cursor [. cget -cursor] \
2145 -spacing1 1 -spacing3 1
2146 lappend bglist $cflist
2147 lappend fglist $cflist
2148 scrollbar .bright.sb -command "$cflist yview"
2149 pack .bright.sb -side right -fill y
2150 pack $cflist -side left -fill both -expand 1
2151 $cflist tag configure highlight \
2152 -background [$cflist cget -selectbackground]
2153 $cflist tag configure bold -font mainfontbold
2155 .pwbottom add .bright
2156 .ctop add .pwbottom
2158 # restore window width & height if known
2159 if {[info exists geometry(main)]} {
2160 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2161 if {$w > [winfo screenwidth .]} {
2162 set w [winfo screenwidth .]
2164 if {$h > [winfo screenheight .]} {
2165 set h [winfo screenheight .]
2167 wm geometry . "${w}x$h"
2171 if {[tk windowingsystem] eq {aqua}} {
2172 set M1B M1
2173 } else {
2174 set M1B Control
2177 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2178 pack .ctop -fill both -expand 1
2179 bindall <1> {selcanvline %W %x %y}
2180 #bindall <B1-Motion> {selcanvline %W %x %y}
2181 if {[tk windowingsystem] == "win32"} {
2182 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2183 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2184 } else {
2185 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2186 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2187 if {[tk windowingsystem] eq "aqua"} {
2188 bindall <MouseWheel> {
2189 set delta [expr {- (%D)}]
2190 allcanvs yview scroll $delta units
2194 bindall <2> "canvscan mark %W %x %y"
2195 bindall <B2-Motion> "canvscan dragto %W %x %y"
2196 bindkey <Home> selfirstline
2197 bindkey <End> sellastline
2198 bind . <Key-Up> "selnextline -1"
2199 bind . <Key-Down> "selnextline 1"
2200 bind . <Shift-Key-Up> "dofind -1 0"
2201 bind . <Shift-Key-Down> "dofind 1 0"
2202 bindkey <Key-Right> "goforw"
2203 bindkey <Key-Left> "goback"
2204 bind . <Key-Prior> "selnextpage -1"
2205 bind . <Key-Next> "selnextpage 1"
2206 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2207 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2208 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2209 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2210 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2211 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2212 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2213 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2214 bindkey <Key-space> "$ctext yview scroll 1 pages"
2215 bindkey p "selnextline -1"
2216 bindkey n "selnextline 1"
2217 bindkey z "goback"
2218 bindkey x "goforw"
2219 bindkey i "selnextline -1"
2220 bindkey k "selnextline 1"
2221 bindkey j "goback"
2222 bindkey l "goforw"
2223 bindkey b prevfile
2224 bindkey d "$ctext yview scroll 18 units"
2225 bindkey u "$ctext yview scroll -18 units"
2226 bindkey / {dofind 1 1}
2227 bindkey <Key-Return> {dofind 1 1}
2228 bindkey ? {dofind -1 1}
2229 bindkey f nextfile
2230 bindkey <F5> updatecommits
2231 bind . <$M1B-q> doquit
2232 bind . <$M1B-f> {dofind 1 1}
2233 bind . <$M1B-g> {dofind 1 0}
2234 bind . <$M1B-r> dosearchback
2235 bind . <$M1B-s> dosearch
2236 bind . <$M1B-equal> {incrfont 1}
2237 bind . <$M1B-plus> {incrfont 1}
2238 bind . <$M1B-KP_Add> {incrfont 1}
2239 bind . <$M1B-minus> {incrfont -1}
2240 bind . <$M1B-KP_Subtract> {incrfont -1}
2241 wm protocol . WM_DELETE_WINDOW doquit
2242 bind . <Destroy> {stop_backends}
2243 bind . <Button-1> "click %W"
2244 bind $fstring <Key-Return> {dofind 1 1}
2245 bind $sha1entry <Key-Return> {gotocommit; break}
2246 bind $sha1entry <<PasteSelection>> clearsha1
2247 bind $cflist <1> {sel_flist %W %x %y; break}
2248 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2249 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2250 global ctxbut
2251 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2253 set maincursor [. cget -cursor]
2254 set textcursor [$ctext cget -cursor]
2255 set curtextcursor $textcursor
2257 set rowctxmenu .rowctxmenu
2258 makemenu $rowctxmenu {
2259 {mc "Diff this -> selected" command {diffvssel 0}}
2260 {mc "Diff selected -> this" command {diffvssel 1}}
2261 {mc "Make patch" command mkpatch}
2262 {mc "Create tag" command mktag}
2263 {mc "Write commit to file" command writecommit}
2264 {mc "Create new branch" command mkbranch}
2265 {mc "Cherry-pick this commit" command cherrypick}
2266 {mc "Reset HEAD branch to here" command resethead}
2268 $rowctxmenu configure -tearoff 0
2270 set fakerowmenu .fakerowmenu
2271 makemenu $fakerowmenu {
2272 {mc "Diff this -> selected" command {diffvssel 0}}
2273 {mc "Diff selected -> this" command {diffvssel 1}}
2274 {mc "Make patch" command mkpatch}
2276 $fakerowmenu configure -tearoff 0
2278 set headctxmenu .headctxmenu
2279 makemenu $headctxmenu {
2280 {mc "Check out this branch" command cobranch}
2281 {mc "Remove this branch" command rmbranch}
2283 $headctxmenu configure -tearoff 0
2285 global flist_menu
2286 set flist_menu .flistctxmenu
2287 makemenu $flist_menu {
2288 {mc "Highlight this too" command {flist_hl 0}}
2289 {mc "Highlight this only" command {flist_hl 1}}
2290 {mc "External diff" command {external_diff}}
2291 {mc "Blame parent commit" command {external_blame 1}}
2293 $flist_menu configure -tearoff 0
2296 # Windows sends all mouse wheel events to the current focused window, not
2297 # the one where the mouse hovers, so bind those events here and redirect
2298 # to the correct window
2299 proc windows_mousewheel_redirector {W X Y D} {
2300 global canv canv2 canv3
2301 set w [winfo containing -displayof $W $X $Y]
2302 if {$w ne ""} {
2303 set u [expr {$D < 0 ? 5 : -5}]
2304 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2305 allcanvs yview scroll $u units
2306 } else {
2307 catch {
2308 $w yview scroll $u units
2314 # Update row number label when selectedline changes
2315 proc selectedline_change {n1 n2 op} {
2316 global selectedline rownumsel
2318 if {$selectedline eq {}} {
2319 set rownumsel {}
2320 } else {
2321 set rownumsel [expr {$selectedline + 1}]
2325 # mouse-2 makes all windows scan vertically, but only the one
2326 # the cursor is in scans horizontally
2327 proc canvscan {op w x y} {
2328 global canv canv2 canv3
2329 foreach c [list $canv $canv2 $canv3] {
2330 if {$c == $w} {
2331 $c scan $op $x $y
2332 } else {
2333 $c scan $op 0 $y
2338 proc scrollcanv {cscroll f0 f1} {
2339 $cscroll set $f0 $f1
2340 drawvisible
2341 flushhighlights
2344 # when we make a key binding for the toplevel, make sure
2345 # it doesn't get triggered when that key is pressed in the
2346 # find string entry widget.
2347 proc bindkey {ev script} {
2348 global entries
2349 bind . $ev $script
2350 set escript [bind Entry $ev]
2351 if {$escript == {}} {
2352 set escript [bind Entry <Key>]
2354 foreach e $entries {
2355 bind $e $ev "$escript; break"
2359 # set the focus back to the toplevel for any click outside
2360 # the entry widgets
2361 proc click {w} {
2362 global ctext entries
2363 foreach e [concat $entries $ctext] {
2364 if {$w == $e} return
2366 focus .
2369 # Adjust the progress bar for a change in requested extent or canvas size
2370 proc adjustprogress {} {
2371 global progresscanv progressitem progresscoords
2372 global fprogitem fprogcoord lastprogupdate progupdatepending
2373 global rprogitem rprogcoord
2375 set w [expr {[winfo width $progresscanv] - 4}]
2376 set x0 [expr {$w * [lindex $progresscoords 0]}]
2377 set x1 [expr {$w * [lindex $progresscoords 1]}]
2378 set h [winfo height $progresscanv]
2379 $progresscanv coords $progressitem $x0 0 $x1 $h
2380 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2381 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2382 set now [clock clicks -milliseconds]
2383 if {$now >= $lastprogupdate + 100} {
2384 set progupdatepending 0
2385 update
2386 } elseif {!$progupdatepending} {
2387 set progupdatepending 1
2388 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2392 proc doprogupdate {} {
2393 global lastprogupdate progupdatepending
2395 if {$progupdatepending} {
2396 set progupdatepending 0
2397 set lastprogupdate [clock clicks -milliseconds]
2398 update
2402 proc savestuff {w} {
2403 global canv canv2 canv3 mainfont textfont uifont tabstop
2404 global stuffsaved findmergefiles maxgraphpct
2405 global maxwidth showneartags showlocalchanges
2406 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2407 global cmitmode wrapcomment datetimeformat limitdiffs
2408 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2409 global autoselect extdifftool perfile_attrs
2411 if {$stuffsaved} return
2412 if {![winfo viewable .]} return
2413 catch {
2414 set f [open "~/.gitk-new" w]
2415 puts $f [list set mainfont $mainfont]
2416 puts $f [list set textfont $textfont]
2417 puts $f [list set uifont $uifont]
2418 puts $f [list set tabstop $tabstop]
2419 puts $f [list set findmergefiles $findmergefiles]
2420 puts $f [list set maxgraphpct $maxgraphpct]
2421 puts $f [list set maxwidth $maxwidth]
2422 puts $f [list set cmitmode $cmitmode]
2423 puts $f [list set wrapcomment $wrapcomment]
2424 puts $f [list set autoselect $autoselect]
2425 puts $f [list set showneartags $showneartags]
2426 puts $f [list set showlocalchanges $showlocalchanges]
2427 puts $f [list set datetimeformat $datetimeformat]
2428 puts $f [list set limitdiffs $limitdiffs]
2429 puts $f [list set bgcolor $bgcolor]
2430 puts $f [list set fgcolor $fgcolor]
2431 puts $f [list set colors $colors]
2432 puts $f [list set diffcolors $diffcolors]
2433 puts $f [list set diffcontext $diffcontext]
2434 puts $f [list set selectbgcolor $selectbgcolor]
2435 puts $f [list set extdifftool $extdifftool]
2436 puts $f [list set perfile_attrs $perfile_attrs]
2438 puts $f "set geometry(main) [wm geometry .]"
2439 puts $f "set geometry(topwidth) [winfo width .tf]"
2440 puts $f "set geometry(topheight) [winfo height .tf]"
2441 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2442 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2443 puts $f "set geometry(botwidth) [winfo width .bleft]"
2444 puts $f "set geometry(botheight) [winfo height .bleft]"
2446 puts -nonewline $f "set permviews {"
2447 for {set v 0} {$v < $nextviewnum} {incr v} {
2448 if {$viewperm($v)} {
2449 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2452 puts $f "}"
2453 close $f
2454 catch {file delete "~/.gitk"}
2455 file rename -force "~/.gitk-new" "~/.gitk"
2457 set stuffsaved 1
2460 proc resizeclistpanes {win w} {
2461 global oldwidth
2462 if {[info exists oldwidth($win)]} {
2463 set s0 [$win sash coord 0]
2464 set s1 [$win sash coord 1]
2465 if {$w < 60} {
2466 set sash0 [expr {int($w/2 - 2)}]
2467 set sash1 [expr {int($w*5/6 - 2)}]
2468 } else {
2469 set factor [expr {1.0 * $w / $oldwidth($win)}]
2470 set sash0 [expr {int($factor * [lindex $s0 0])}]
2471 set sash1 [expr {int($factor * [lindex $s1 0])}]
2472 if {$sash0 < 30} {
2473 set sash0 30
2475 if {$sash1 < $sash0 + 20} {
2476 set sash1 [expr {$sash0 + 20}]
2478 if {$sash1 > $w - 10} {
2479 set sash1 [expr {$w - 10}]
2480 if {$sash0 > $sash1 - 20} {
2481 set sash0 [expr {$sash1 - 20}]
2485 $win sash place 0 $sash0 [lindex $s0 1]
2486 $win sash place 1 $sash1 [lindex $s1 1]
2488 set oldwidth($win) $w
2491 proc resizecdetpanes {win w} {
2492 global oldwidth
2493 if {[info exists oldwidth($win)]} {
2494 set s0 [$win sash coord 0]
2495 if {$w < 60} {
2496 set sash0 [expr {int($w*3/4 - 2)}]
2497 } else {
2498 set factor [expr {1.0 * $w / $oldwidth($win)}]
2499 set sash0 [expr {int($factor * [lindex $s0 0])}]
2500 if {$sash0 < 45} {
2501 set sash0 45
2503 if {$sash0 > $w - 15} {
2504 set sash0 [expr {$w - 15}]
2507 $win sash place 0 $sash0 [lindex $s0 1]
2509 set oldwidth($win) $w
2512 proc allcanvs args {
2513 global canv canv2 canv3
2514 eval $canv $args
2515 eval $canv2 $args
2516 eval $canv3 $args
2519 proc bindall {event action} {
2520 global canv canv2 canv3
2521 bind $canv $event $action
2522 bind $canv2 $event $action
2523 bind $canv3 $event $action
2526 proc about {} {
2527 global uifont
2528 set w .about
2529 if {[winfo exists $w]} {
2530 raise $w
2531 return
2533 toplevel $w
2534 wm title $w [mc "About gitk"]
2535 message $w.m -text [mc "
2536 Gitk - a commit viewer for git
2538 Copyright © 2005-2008 Paul Mackerras
2540 Use and redistribute under the terms of the GNU General Public License"] \
2541 -justify center -aspect 400 -border 2 -bg white -relief groove
2542 pack $w.m -side top -fill x -padx 2 -pady 2
2543 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2544 pack $w.ok -side bottom
2545 bind $w <Visibility> "focus $w.ok"
2546 bind $w <Key-Escape> "destroy $w"
2547 bind $w <Key-Return> "destroy $w"
2550 proc keys {} {
2551 set w .keys
2552 if {[winfo exists $w]} {
2553 raise $w
2554 return
2556 if {[tk windowingsystem] eq {aqua}} {
2557 set M1T Cmd
2558 } else {
2559 set M1T Ctrl
2561 toplevel $w
2562 wm title $w [mc "Gitk key bindings"]
2563 message $w.m -text "
2564 [mc "Gitk key bindings:"]
2566 [mc "<%s-Q> Quit" $M1T]
2567 [mc "<Home> Move to first commit"]
2568 [mc "<End> Move to last commit"]
2569 [mc "<Up>, p, i Move up one commit"]
2570 [mc "<Down>, n, k Move down one commit"]
2571 [mc "<Left>, z, j Go back in history list"]
2572 [mc "<Right>, x, l Go forward in history list"]
2573 [mc "<PageUp> Move up one page in commit list"]
2574 [mc "<PageDown> Move down one page in commit list"]
2575 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2576 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2577 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2578 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2579 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2580 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2581 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2582 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2583 [mc "<Delete>, b Scroll diff view up one page"]
2584 [mc "<Backspace> Scroll diff view up one page"]
2585 [mc "<Space> Scroll diff view down one page"]
2586 [mc "u Scroll diff view up 18 lines"]
2587 [mc "d Scroll diff view down 18 lines"]
2588 [mc "<%s-F> Find" $M1T]
2589 [mc "<%s-G> Move to next find hit" $M1T]
2590 [mc "<Return> Move to next find hit"]
2591 [mc "/ Move to next find hit, or redo find"]
2592 [mc "? Move to previous find hit"]
2593 [mc "f Scroll diff view to next file"]
2594 [mc "<%s-S> Search for next hit in diff view" $M1T]
2595 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2596 [mc "<%s-KP+> Increase font size" $M1T]
2597 [mc "<%s-plus> Increase font size" $M1T]
2598 [mc "<%s-KP-> Decrease font size" $M1T]
2599 [mc "<%s-minus> Decrease font size" $M1T]
2600 [mc "<F5> Update"]
2602 -justify left -bg white -border 2 -relief groove
2603 pack $w.m -side top -fill both -padx 2 -pady 2
2604 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2605 pack $w.ok -side bottom
2606 bind $w <Visibility> "focus $w.ok"
2607 bind $w <Key-Escape> "destroy $w"
2608 bind $w <Key-Return> "destroy $w"
2611 # Procedures for manipulating the file list window at the
2612 # bottom right of the overall window.
2614 proc treeview {w l openlevs} {
2615 global treecontents treediropen treeheight treeparent treeindex
2617 set ix 0
2618 set treeindex() 0
2619 set lev 0
2620 set prefix {}
2621 set prefixend -1
2622 set prefendstack {}
2623 set htstack {}
2624 set ht 0
2625 set treecontents() {}
2626 $w conf -state normal
2627 foreach f $l {
2628 while {[string range $f 0 $prefixend] ne $prefix} {
2629 if {$lev <= $openlevs} {
2630 $w mark set e:$treeindex($prefix) "end -1c"
2631 $w mark gravity e:$treeindex($prefix) left
2633 set treeheight($prefix) $ht
2634 incr ht [lindex $htstack end]
2635 set htstack [lreplace $htstack end end]
2636 set prefixend [lindex $prefendstack end]
2637 set prefendstack [lreplace $prefendstack end end]
2638 set prefix [string range $prefix 0 $prefixend]
2639 incr lev -1
2641 set tail [string range $f [expr {$prefixend+1}] end]
2642 while {[set slash [string first "/" $tail]] >= 0} {
2643 lappend htstack $ht
2644 set ht 0
2645 lappend prefendstack $prefixend
2646 incr prefixend [expr {$slash + 1}]
2647 set d [string range $tail 0 $slash]
2648 lappend treecontents($prefix) $d
2649 set oldprefix $prefix
2650 append prefix $d
2651 set treecontents($prefix) {}
2652 set treeindex($prefix) [incr ix]
2653 set treeparent($prefix) $oldprefix
2654 set tail [string range $tail [expr {$slash+1}] end]
2655 if {$lev <= $openlevs} {
2656 set ht 1
2657 set treediropen($prefix) [expr {$lev < $openlevs}]
2658 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2659 $w mark set d:$ix "end -1c"
2660 $w mark gravity d:$ix left
2661 set str "\n"
2662 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2663 $w insert end $str
2664 $w image create end -align center -image $bm -padx 1 \
2665 -name a:$ix
2666 $w insert end $d [highlight_tag $prefix]
2667 $w mark set s:$ix "end -1c"
2668 $w mark gravity s:$ix left
2670 incr lev
2672 if {$tail ne {}} {
2673 if {$lev <= $openlevs} {
2674 incr ht
2675 set str "\n"
2676 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2677 $w insert end $str
2678 $w insert end $tail [highlight_tag $f]
2680 lappend treecontents($prefix) $tail
2683 while {$htstack ne {}} {
2684 set treeheight($prefix) $ht
2685 incr ht [lindex $htstack end]
2686 set htstack [lreplace $htstack end end]
2687 set prefixend [lindex $prefendstack end]
2688 set prefendstack [lreplace $prefendstack end end]
2689 set prefix [string range $prefix 0 $prefixend]
2691 $w conf -state disabled
2694 proc linetoelt {l} {
2695 global treeheight treecontents
2697 set y 2
2698 set prefix {}
2699 while {1} {
2700 foreach e $treecontents($prefix) {
2701 if {$y == $l} {
2702 return "$prefix$e"
2704 set n 1
2705 if {[string index $e end] eq "/"} {
2706 set n $treeheight($prefix$e)
2707 if {$y + $n > $l} {
2708 append prefix $e
2709 incr y
2710 break
2713 incr y $n
2718 proc highlight_tree {y prefix} {
2719 global treeheight treecontents cflist
2721 foreach e $treecontents($prefix) {
2722 set path $prefix$e
2723 if {[highlight_tag $path] ne {}} {
2724 $cflist tag add bold $y.0 "$y.0 lineend"
2726 incr y
2727 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2728 set y [highlight_tree $y $path]
2731 return $y
2734 proc treeclosedir {w dir} {
2735 global treediropen treeheight treeparent treeindex
2737 set ix $treeindex($dir)
2738 $w conf -state normal
2739 $w delete s:$ix e:$ix
2740 set treediropen($dir) 0
2741 $w image configure a:$ix -image tri-rt
2742 $w conf -state disabled
2743 set n [expr {1 - $treeheight($dir)}]
2744 while {$dir ne {}} {
2745 incr treeheight($dir) $n
2746 set dir $treeparent($dir)
2750 proc treeopendir {w dir} {
2751 global treediropen treeheight treeparent treecontents treeindex
2753 set ix $treeindex($dir)
2754 $w conf -state normal
2755 $w image configure a:$ix -image tri-dn
2756 $w mark set e:$ix s:$ix
2757 $w mark gravity e:$ix right
2758 set lev 0
2759 set str "\n"
2760 set n [llength $treecontents($dir)]
2761 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2762 incr lev
2763 append str "\t"
2764 incr treeheight($x) $n
2766 foreach e $treecontents($dir) {
2767 set de $dir$e
2768 if {[string index $e end] eq "/"} {
2769 set iy $treeindex($de)
2770 $w mark set d:$iy e:$ix
2771 $w mark gravity d:$iy left
2772 $w insert e:$ix $str
2773 set treediropen($de) 0
2774 $w image create e:$ix -align center -image tri-rt -padx 1 \
2775 -name a:$iy
2776 $w insert e:$ix $e [highlight_tag $de]
2777 $w mark set s:$iy e:$ix
2778 $w mark gravity s:$iy left
2779 set treeheight($de) 1
2780 } else {
2781 $w insert e:$ix $str
2782 $w insert e:$ix $e [highlight_tag $de]
2785 $w mark gravity e:$ix right
2786 $w conf -state disabled
2787 set treediropen($dir) 1
2788 set top [lindex [split [$w index @0,0] .] 0]
2789 set ht [$w cget -height]
2790 set l [lindex [split [$w index s:$ix] .] 0]
2791 if {$l < $top} {
2792 $w yview $l.0
2793 } elseif {$l + $n + 1 > $top + $ht} {
2794 set top [expr {$l + $n + 2 - $ht}]
2795 if {$l < $top} {
2796 set top $l
2798 $w yview $top.0
2802 proc treeclick {w x y} {
2803 global treediropen cmitmode ctext cflist cflist_top
2805 if {$cmitmode ne "tree"} return
2806 if {![info exists cflist_top]} return
2807 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2808 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2809 $cflist tag add highlight $l.0 "$l.0 lineend"
2810 set cflist_top $l
2811 if {$l == 1} {
2812 $ctext yview 1.0
2813 return
2815 set e [linetoelt $l]
2816 if {[string index $e end] ne "/"} {
2817 showfile $e
2818 } elseif {$treediropen($e)} {
2819 treeclosedir $w $e
2820 } else {
2821 treeopendir $w $e
2825 proc setfilelist {id} {
2826 global treefilelist cflist
2828 treeview $cflist $treefilelist($id) 0
2831 image create bitmap tri-rt -background black -foreground blue -data {
2832 #define tri-rt_width 13
2833 #define tri-rt_height 13
2834 static unsigned char tri-rt_bits[] = {
2835 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2836 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2837 0x00, 0x00};
2838 } -maskdata {
2839 #define tri-rt-mask_width 13
2840 #define tri-rt-mask_height 13
2841 static unsigned char tri-rt-mask_bits[] = {
2842 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2843 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2844 0x08, 0x00};
2846 image create bitmap tri-dn -background black -foreground blue -data {
2847 #define tri-dn_width 13
2848 #define tri-dn_height 13
2849 static unsigned char tri-dn_bits[] = {
2850 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2851 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2852 0x00, 0x00};
2853 } -maskdata {
2854 #define tri-dn-mask_width 13
2855 #define tri-dn-mask_height 13
2856 static unsigned char tri-dn-mask_bits[] = {
2857 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2858 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2859 0x00, 0x00};
2862 image create bitmap reficon-T -background black -foreground yellow -data {
2863 #define tagicon_width 13
2864 #define tagicon_height 9
2865 static unsigned char tagicon_bits[] = {
2866 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2867 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2868 } -maskdata {
2869 #define tagicon-mask_width 13
2870 #define tagicon-mask_height 9
2871 static unsigned char tagicon-mask_bits[] = {
2872 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2873 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2875 set rectdata {
2876 #define headicon_width 13
2877 #define headicon_height 9
2878 static unsigned char headicon_bits[] = {
2879 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2880 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2882 set rectmask {
2883 #define headicon-mask_width 13
2884 #define headicon-mask_height 9
2885 static unsigned char headicon-mask_bits[] = {
2886 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2887 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2889 image create bitmap reficon-H -background black -foreground green \
2890 -data $rectdata -maskdata $rectmask
2891 image create bitmap reficon-o -background black -foreground "#ddddff" \
2892 -data $rectdata -maskdata $rectmask
2894 proc init_flist {first} {
2895 global cflist cflist_top difffilestart
2897 $cflist conf -state normal
2898 $cflist delete 0.0 end
2899 if {$first ne {}} {
2900 $cflist insert end $first
2901 set cflist_top 1
2902 $cflist tag add highlight 1.0 "1.0 lineend"
2903 } else {
2904 catch {unset cflist_top}
2906 $cflist conf -state disabled
2907 set difffilestart {}
2910 proc highlight_tag {f} {
2911 global highlight_paths
2913 foreach p $highlight_paths {
2914 if {[string match $p $f]} {
2915 return "bold"
2918 return {}
2921 proc highlight_filelist {} {
2922 global cmitmode cflist
2924 $cflist conf -state normal
2925 if {$cmitmode ne "tree"} {
2926 set end [lindex [split [$cflist index end] .] 0]
2927 for {set l 2} {$l < $end} {incr l} {
2928 set line [$cflist get $l.0 "$l.0 lineend"]
2929 if {[highlight_tag $line] ne {}} {
2930 $cflist tag add bold $l.0 "$l.0 lineend"
2933 } else {
2934 highlight_tree 2 {}
2936 $cflist conf -state disabled
2939 proc unhighlight_filelist {} {
2940 global cflist
2942 $cflist conf -state normal
2943 $cflist tag remove bold 1.0 end
2944 $cflist conf -state disabled
2947 proc add_flist {fl} {
2948 global cflist
2950 $cflist conf -state normal
2951 foreach f $fl {
2952 $cflist insert end "\n"
2953 $cflist insert end $f [highlight_tag $f]
2955 $cflist conf -state disabled
2958 proc sel_flist {w x y} {
2959 global ctext difffilestart cflist cflist_top cmitmode
2961 if {$cmitmode eq "tree"} return
2962 if {![info exists cflist_top]} return
2963 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2964 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2965 $cflist tag add highlight $l.0 "$l.0 lineend"
2966 set cflist_top $l
2967 if {$l == 1} {
2968 $ctext yview 1.0
2969 } else {
2970 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2974 proc pop_flist_menu {w X Y x y} {
2975 global ctext cflist cmitmode flist_menu flist_menu_file
2976 global treediffs diffids
2978 stopfinding
2979 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2980 if {$l <= 1} return
2981 if {$cmitmode eq "tree"} {
2982 set e [linetoelt $l]
2983 if {[string index $e end] eq "/"} return
2984 } else {
2985 set e [lindex $treediffs($diffids) [expr {$l-2}]]
2987 set flist_menu_file $e
2988 set xdiffstate "normal"
2989 if {$cmitmode eq "tree"} {
2990 set xdiffstate "disabled"
2992 # Disable "External diff" item in tree mode
2993 $flist_menu entryconf 2 -state $xdiffstate
2994 tk_popup $flist_menu $X $Y
2997 proc flist_hl {only} {
2998 global flist_menu_file findstring gdttype
3000 set x [shellquote $flist_menu_file]
3001 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3002 set findstring $x
3003 } else {
3004 append findstring " " $x
3006 set gdttype [mc "touching paths:"]
3009 proc save_file_from_commit {filename output what} {
3010 global nullfile
3012 if {[catch {exec git show $filename -- > $output} err]} {
3013 if {[string match "fatal: bad revision *" $err]} {
3014 return $nullfile
3016 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3017 return {}
3019 return $output
3022 proc external_diff_get_one_file {diffid filename diffdir} {
3023 global nullid nullid2 nullfile
3024 global gitdir
3026 if {$diffid == $nullid} {
3027 set difffile [file join [file dirname $gitdir] $filename]
3028 if {[file exists $difffile]} {
3029 return $difffile
3031 return $nullfile
3033 if {$diffid == $nullid2} {
3034 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3035 return [save_file_from_commit :$filename $difffile index]
3037 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3038 return [save_file_from_commit $diffid:$filename $difffile \
3039 "revision $diffid"]
3042 proc external_diff {} {
3043 global gitktmpdir nullid nullid2
3044 global flist_menu_file
3045 global diffids
3046 global diffnum
3047 global gitdir extdifftool
3049 if {[llength $diffids] == 1} {
3050 # no reference commit given
3051 set diffidto [lindex $diffids 0]
3052 if {$diffidto eq $nullid} {
3053 # diffing working copy with index
3054 set diffidfrom $nullid2
3055 } elseif {$diffidto eq $nullid2} {
3056 # diffing index with HEAD
3057 set diffidfrom "HEAD"
3058 } else {
3059 # use first parent commit
3060 global parentlist selectedline
3061 set diffidfrom [lindex $parentlist $selectedline 0]
3063 } else {
3064 set diffidfrom [lindex $diffids 0]
3065 set diffidto [lindex $diffids 1]
3068 # make sure that several diffs wont collide
3069 if {![info exists gitktmpdir]} {
3070 set gitktmpdir [file join [file dirname $gitdir] \
3071 [format ".gitk-tmp.%s" [pid]]]
3072 if {[catch {file mkdir $gitktmpdir} err]} {
3073 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3074 unset gitktmpdir
3075 return
3077 set diffnum 0
3079 incr diffnum
3080 set diffdir [file join $gitktmpdir $diffnum]
3081 if {[catch {file mkdir $diffdir} err]} {
3082 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3083 return
3086 # gather files to diff
3087 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3088 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3090 if {$difffromfile ne {} && $difftofile ne {}} {
3091 set cmd [concat | [shellsplit $extdifftool] \
3092 [list $difffromfile $difftofile]]
3093 if {[catch {set fl [open $cmd r]} err]} {
3094 file delete -force $diffdir
3095 error_popup "$extdifftool: [mc "command failed:"] $err"
3096 } else {
3097 fconfigure $fl -blocking 0
3098 filerun $fl [list delete_at_eof $fl $diffdir]
3103 proc external_blame {parent_idx} {
3104 global flist_menu_file
3105 global nullid nullid2
3106 global parentlist selectedline currentid
3108 if {$parent_idx > 0} {
3109 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3110 } else {
3111 set base_commit $currentid
3114 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3115 error_popup [mc "No such commit"]
3116 return
3119 if {[catch {exec git gui blame $base_commit $flist_menu_file &} err]} {
3120 error_popup "[mc "git gui blame: command failed:"] $err"
3124 # delete $dir when we see eof on $f (presumably because the child has exited)
3125 proc delete_at_eof {f dir} {
3126 while {[gets $f line] >= 0} {}
3127 if {[eof $f]} {
3128 if {[catch {close $f} err]} {
3129 error_popup "[mc "External diff viewer failed:"] $err"
3131 file delete -force $dir
3132 return 0
3134 return 1
3137 # Functions for adding and removing shell-type quoting
3139 proc shellquote {str} {
3140 if {![string match "*\['\"\\ \t]*" $str]} {
3141 return $str
3143 if {![string match "*\['\"\\]*" $str]} {
3144 return "\"$str\""
3146 if {![string match "*'*" $str]} {
3147 return "'$str'"
3149 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3152 proc shellarglist {l} {
3153 set str {}
3154 foreach a $l {
3155 if {$str ne {}} {
3156 append str " "
3158 append str [shellquote $a]
3160 return $str
3163 proc shelldequote {str} {
3164 set ret {}
3165 set used -1
3166 while {1} {
3167 incr used
3168 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3169 append ret [string range $str $used end]
3170 set used [string length $str]
3171 break
3173 set first [lindex $first 0]
3174 set ch [string index $str $first]
3175 if {$first > $used} {
3176 append ret [string range $str $used [expr {$first - 1}]]
3177 set used $first
3179 if {$ch eq " " || $ch eq "\t"} break
3180 incr used
3181 if {$ch eq "'"} {
3182 set first [string first "'" $str $used]
3183 if {$first < 0} {
3184 error "unmatched single-quote"
3186 append ret [string range $str $used [expr {$first - 1}]]
3187 set used $first
3188 continue
3190 if {$ch eq "\\"} {
3191 if {$used >= [string length $str]} {
3192 error "trailing backslash"
3194 append ret [string index $str $used]
3195 continue
3197 # here ch == "\""
3198 while {1} {
3199 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3200 error "unmatched double-quote"
3202 set first [lindex $first 0]
3203 set ch [string index $str $first]
3204 if {$first > $used} {
3205 append ret [string range $str $used [expr {$first - 1}]]
3206 set used $first
3208 if {$ch eq "\""} break
3209 incr used
3210 append ret [string index $str $used]
3211 incr used
3214 return [list $used $ret]
3217 proc shellsplit {str} {
3218 set l {}
3219 while {1} {
3220 set str [string trimleft $str]
3221 if {$str eq {}} break
3222 set dq [shelldequote $str]
3223 set n [lindex $dq 0]
3224 set word [lindex $dq 1]
3225 set str [string range $str $n end]
3226 lappend l $word
3228 return $l
3231 # Code to implement multiple views
3233 proc newview {ishighlight} {
3234 global nextviewnum newviewname newviewperm newishighlight
3235 global newviewargs revtreeargs viewargscmd newviewargscmd curview
3237 set newishighlight $ishighlight
3238 set top .gitkview
3239 if {[winfo exists $top]} {
3240 raise $top
3241 return
3243 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3244 set newviewperm($nextviewnum) 0
3245 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
3246 set newviewargscmd($nextviewnum) $viewargscmd($curview)
3247 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3250 proc editview {} {
3251 global curview
3252 global viewname viewperm newviewname newviewperm
3253 global viewargs newviewargs viewargscmd newviewargscmd
3255 set top .gitkvedit-$curview
3256 if {[winfo exists $top]} {
3257 raise $top
3258 return
3260 set newviewname($curview) $viewname($curview)
3261 set newviewperm($curview) $viewperm($curview)
3262 set newviewargs($curview) [shellarglist $viewargs($curview)]
3263 set newviewargscmd($curview) $viewargscmd($curview)
3264 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3267 proc vieweditor {top n title} {
3268 global newviewname newviewperm viewfiles bgcolor
3270 toplevel $top
3271 wm title $top $title
3272 label $top.nl -text [mc "Name"]
3273 entry $top.name -width 20 -textvariable newviewname($n)
3274 grid $top.nl $top.name -sticky w -pady 5
3275 checkbutton $top.perm -text [mc "Remember this view"] \
3276 -variable newviewperm($n)
3277 grid $top.perm - -pady 5 -sticky w
3278 message $top.al -aspect 1000 \
3279 -text [mc "Commits to include (arguments to git log):"]
3280 grid $top.al - -sticky w -pady 5
3281 entry $top.args -width 50 -textvariable newviewargs($n) \
3282 -background $bgcolor
3283 grid $top.args - -sticky ew -padx 5
3285 message $top.ac -aspect 1000 \
3286 -text [mc "Command to generate more commits to include:"]
3287 grid $top.ac - -sticky w -pady 5
3288 entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
3289 -background white
3290 grid $top.argscmd - -sticky ew -padx 5
3292 message $top.l -aspect 1000 \
3293 -text [mc "Enter files and directories to include, one per line:"]
3294 grid $top.l - -sticky w
3295 text $top.t -width 40 -height 10 -background $bgcolor -font uifont
3296 if {[info exists viewfiles($n)]} {
3297 foreach f $viewfiles($n) {
3298 $top.t insert end $f
3299 $top.t insert end "\n"
3301 $top.t delete {end - 1c} end
3302 $top.t mark set insert 0.0
3304 grid $top.t - -sticky ew -padx 5
3305 frame $top.buts
3306 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3307 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3308 grid $top.buts.ok $top.buts.can
3309 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3310 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3311 grid $top.buts - -pady 10 -sticky ew
3312 focus $top.t
3315 proc doviewmenu {m first cmd op argv} {
3316 set nmenu [$m index end]
3317 for {set i $first} {$i <= $nmenu} {incr i} {
3318 if {[$m entrycget $i -command] eq $cmd} {
3319 eval $m $op $i $argv
3320 break
3325 proc allviewmenus {n op args} {
3326 # global viewhlmenu
3328 doviewmenu .bar.view 5 [list showview $n] $op $args
3329 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3332 proc newviewok {top n} {
3333 global nextviewnum newviewperm newviewname newishighlight
3334 global viewname viewfiles viewperm selectedview curview
3335 global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
3337 if {[catch {
3338 set newargs [shellsplit $newviewargs($n)]
3339 } err]} {
3340 error_popup "[mc "Error in commit selection arguments:"] $err"
3341 wm raise $top
3342 focus $top
3343 return
3345 set files {}
3346 foreach f [split [$top.t get 0.0 end] "\n"] {
3347 set ft [string trim $f]
3348 if {$ft ne {}} {
3349 lappend files $ft
3352 if {![info exists viewfiles($n)]} {
3353 # creating a new view
3354 incr nextviewnum
3355 set viewname($n) $newviewname($n)
3356 set viewperm($n) $newviewperm($n)
3357 set viewfiles($n) $files
3358 set viewargs($n) $newargs
3359 set viewargscmd($n) $newviewargscmd($n)
3360 addviewmenu $n
3361 if {!$newishighlight} {
3362 run showview $n
3363 } else {
3364 run addvhighlight $n
3366 } else {
3367 # editing an existing view
3368 set viewperm($n) $newviewperm($n)
3369 if {$newviewname($n) ne $viewname($n)} {
3370 set viewname($n) $newviewname($n)
3371 doviewmenu .bar.view 5 [list showview $n] \
3372 entryconf [list -label $viewname($n)]
3373 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3374 # entryconf [list -label $viewname($n) -value $viewname($n)]
3376 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3377 $newviewargscmd($n) ne $viewargscmd($n)} {
3378 set viewfiles($n) $files
3379 set viewargs($n) $newargs
3380 set viewargscmd($n) $newviewargscmd($n)
3381 if {$curview == $n} {
3382 run reloadcommits
3386 catch {destroy $top}
3389 proc delview {} {
3390 global curview viewperm hlview selectedhlview
3392 if {$curview == 0} return
3393 if {[info exists hlview] && $hlview == $curview} {
3394 set selectedhlview [mc "None"]
3395 unset hlview
3397 allviewmenus $curview delete
3398 set viewperm($curview) 0
3399 showview 0
3402 proc addviewmenu {n} {
3403 global viewname viewhlmenu
3405 .bar.view add radiobutton -label $viewname($n) \
3406 -command [list showview $n] -variable selectedview -value $n
3407 #$viewhlmenu add radiobutton -label $viewname($n) \
3408 # -command [list addvhighlight $n] -variable selectedhlview
3411 proc showview {n} {
3412 global curview cached_commitrow ordertok
3413 global displayorder parentlist rowidlist rowisopt rowfinal
3414 global colormap rowtextx nextcolor canvxmax
3415 global numcommits viewcomplete
3416 global selectedline currentid canv canvy0
3417 global treediffs
3418 global pending_select mainheadid
3419 global commitidx
3420 global selectedview
3421 global hlview selectedhlview commitinterest
3423 if {$n == $curview} return
3424 set selid {}
3425 set ymax [lindex [$canv cget -scrollregion] 3]
3426 set span [$canv yview]
3427 set ytop [expr {[lindex $span 0] * $ymax}]
3428 set ybot [expr {[lindex $span 1] * $ymax}]
3429 set yscreen [expr {($ybot - $ytop) / 2}]
3430 if {$selectedline ne {}} {
3431 set selid $currentid
3432 set y [yc $selectedline]
3433 if {$ytop < $y && $y < $ybot} {
3434 set yscreen [expr {$y - $ytop}]
3436 } elseif {[info exists pending_select]} {
3437 set selid $pending_select
3438 unset pending_select
3440 unselectline
3441 normalline
3442 catch {unset treediffs}
3443 clear_display
3444 if {[info exists hlview] && $hlview == $n} {
3445 unset hlview
3446 set selectedhlview [mc "None"]
3448 catch {unset commitinterest}
3449 catch {unset cached_commitrow}
3450 catch {unset ordertok}
3452 set curview $n
3453 set selectedview $n
3454 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3455 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3457 run refill_reflist
3458 if {![info exists viewcomplete($n)]} {
3459 getcommits $selid
3460 return
3463 set displayorder {}
3464 set parentlist {}
3465 set rowidlist {}
3466 set rowisopt {}
3467 set rowfinal {}
3468 set numcommits $commitidx($n)
3470 catch {unset colormap}
3471 catch {unset rowtextx}
3472 set nextcolor 0
3473 set canvxmax [$canv cget -width]
3474 set curview $n
3475 set row 0
3476 setcanvscroll
3477 set yf 0
3478 set row {}
3479 if {$selid ne {} && [commitinview $selid $n]} {
3480 set row [rowofcommit $selid]
3481 # try to get the selected row in the same position on the screen
3482 set ymax [lindex [$canv cget -scrollregion] 3]
3483 set ytop [expr {[yc $row] - $yscreen}]
3484 if {$ytop < 0} {
3485 set ytop 0
3487 set yf [expr {$ytop * 1.0 / $ymax}]
3489 allcanvs yview moveto $yf
3490 drawvisible
3491 if {$row ne {}} {
3492 selectline $row 0
3493 } elseif {!$viewcomplete($n)} {
3494 reset_pending_select $selid
3495 } else {
3496 reset_pending_select {}
3498 if {[commitinview $pending_select $curview]} {
3499 selectline [rowofcommit $pending_select] 1
3500 } else {
3501 set row [first_real_row]
3502 if {$row < $numcommits} {
3503 selectline $row 0
3507 if {!$viewcomplete($n)} {
3508 if {$numcommits == 0} {
3509 show_status [mc "Reading commits..."]
3511 } elseif {$numcommits == 0} {
3512 show_status [mc "No commits selected"]
3516 # Stuff relating to the highlighting facility
3518 proc ishighlighted {id} {
3519 global vhighlights fhighlights nhighlights rhighlights
3521 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3522 return $nhighlights($id)
3524 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3525 return $vhighlights($id)
3527 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3528 return $fhighlights($id)
3530 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3531 return $rhighlights($id)
3533 return 0
3536 proc bolden {row font} {
3537 global canv linehtag selectedline boldrows
3539 lappend boldrows $row
3540 $canv itemconf $linehtag($row) -font $font
3541 if {$row == $selectedline} {
3542 $canv delete secsel
3543 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3544 -outline {{}} -tags secsel \
3545 -fill [$canv cget -selectbackground]]
3546 $canv lower $t
3550 proc bolden_name {row font} {
3551 global canv2 linentag selectedline boldnamerows
3553 lappend boldnamerows $row
3554 $canv2 itemconf $linentag($row) -font $font
3555 if {$row == $selectedline} {
3556 $canv2 delete secsel
3557 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3558 -outline {{}} -tags secsel \
3559 -fill [$canv2 cget -selectbackground]]
3560 $canv2 lower $t
3564 proc unbolden {} {
3565 global boldrows
3567 set stillbold {}
3568 foreach row $boldrows {
3569 if {![ishighlighted [commitonrow $row]]} {
3570 bolden $row mainfont
3571 } else {
3572 lappend stillbold $row
3575 set boldrows $stillbold
3578 proc addvhighlight {n} {
3579 global hlview viewcomplete curview vhl_done commitidx
3581 if {[info exists hlview]} {
3582 delvhighlight
3584 set hlview $n
3585 if {$n != $curview && ![info exists viewcomplete($n)]} {
3586 start_rev_list $n
3588 set vhl_done $commitidx($hlview)
3589 if {$vhl_done > 0} {
3590 drawvisible
3594 proc delvhighlight {} {
3595 global hlview vhighlights
3597 if {![info exists hlview]} return
3598 unset hlview
3599 catch {unset vhighlights}
3600 unbolden
3603 proc vhighlightmore {} {
3604 global hlview vhl_done commitidx vhighlights curview
3606 set max $commitidx($hlview)
3607 set vr [visiblerows]
3608 set r0 [lindex $vr 0]
3609 set r1 [lindex $vr 1]
3610 for {set i $vhl_done} {$i < $max} {incr i} {
3611 set id [commitonrow $i $hlview]
3612 if {[commitinview $id $curview]} {
3613 set row [rowofcommit $id]
3614 if {$r0 <= $row && $row <= $r1} {
3615 if {![highlighted $row]} {
3616 bolden $row mainfontbold
3618 set vhighlights($id) 1
3622 set vhl_done $max
3623 return 0
3626 proc askvhighlight {row id} {
3627 global hlview vhighlights iddrawn
3629 if {[commitinview $id $hlview]} {
3630 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
3631 bolden $row mainfontbold
3633 set vhighlights($id) 1
3634 } else {
3635 set vhighlights($id) 0
3639 proc hfiles_change {} {
3640 global highlight_files filehighlight fhighlights fh_serial
3641 global highlight_paths gdttype
3643 if {[info exists filehighlight]} {
3644 # delete previous highlights
3645 catch {close $filehighlight}
3646 unset filehighlight
3647 catch {unset fhighlights}
3648 unbolden
3649 unhighlight_filelist
3651 set highlight_paths {}
3652 after cancel do_file_hl $fh_serial
3653 incr fh_serial
3654 if {$highlight_files ne {}} {
3655 after 300 do_file_hl $fh_serial
3659 proc gdttype_change {name ix op} {
3660 global gdttype highlight_files findstring findpattern
3662 stopfinding
3663 if {$findstring ne {}} {
3664 if {$gdttype eq [mc "containing:"]} {
3665 if {$highlight_files ne {}} {
3666 set highlight_files {}
3667 hfiles_change
3669 findcom_change
3670 } else {
3671 if {$findpattern ne {}} {
3672 set findpattern {}
3673 findcom_change
3675 set highlight_files $findstring
3676 hfiles_change
3678 drawvisible
3680 # enable/disable findtype/findloc menus too
3683 proc find_change {name ix op} {
3684 global gdttype findstring highlight_files
3686 stopfinding
3687 if {$gdttype eq [mc "containing:"]} {
3688 findcom_change
3689 } else {
3690 if {$highlight_files ne $findstring} {
3691 set highlight_files $findstring
3692 hfiles_change
3695 drawvisible
3698 proc findcom_change args {
3699 global nhighlights boldnamerows
3700 global findpattern findtype findstring gdttype
3702 stopfinding
3703 # delete previous highlights, if any
3704 foreach row $boldnamerows {
3705 bolden_name $row mainfont
3707 set boldnamerows {}
3708 catch {unset nhighlights}
3709 unbolden
3710 unmarkmatches
3711 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
3712 set findpattern {}
3713 } elseif {$findtype eq [mc "Regexp"]} {
3714 set findpattern $findstring
3715 } else {
3716 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3717 $findstring]
3718 set findpattern "*$e*"
3722 proc makepatterns {l} {
3723 set ret {}
3724 foreach e $l {
3725 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3726 if {[string index $ee end] eq "/"} {
3727 lappend ret "$ee*"
3728 } else {
3729 lappend ret $ee
3730 lappend ret "$ee/*"
3733 return $ret
3736 proc do_file_hl {serial} {
3737 global highlight_files filehighlight highlight_paths gdttype fhl_list
3739 if {$gdttype eq [mc "touching paths:"]} {
3740 if {[catch {set paths [shellsplit $highlight_files]}]} return
3741 set highlight_paths [makepatterns $paths]
3742 highlight_filelist
3743 set gdtargs [concat -- $paths]
3744 } elseif {$gdttype eq [mc "adding/removing string:"]} {
3745 set gdtargs [list "-S$highlight_files"]
3746 } else {
3747 # must be "containing:", i.e. we're searching commit info
3748 return
3750 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3751 set filehighlight [open $cmd r+]
3752 fconfigure $filehighlight -blocking 0
3753 filerun $filehighlight readfhighlight
3754 set fhl_list {}
3755 drawvisible
3756 flushhighlights
3759 proc flushhighlights {} {
3760 global filehighlight fhl_list
3762 if {[info exists filehighlight]} {
3763 lappend fhl_list {}
3764 puts $filehighlight ""
3765 flush $filehighlight
3769 proc askfilehighlight {row id} {
3770 global filehighlight fhighlights fhl_list
3772 lappend fhl_list $id
3773 set fhighlights($id) -1
3774 puts $filehighlight $id
3777 proc readfhighlight {} {
3778 global filehighlight fhighlights curview iddrawn
3779 global fhl_list find_dirn
3781 if {![info exists filehighlight]} {
3782 return 0
3784 set nr 0
3785 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
3786 set line [string trim $line]
3787 set i [lsearch -exact $fhl_list $line]
3788 if {$i < 0} continue
3789 for {set j 0} {$j < $i} {incr j} {
3790 set id [lindex $fhl_list $j]
3791 set fhighlights($id) 0
3793 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
3794 if {$line eq {}} continue
3795 if {![commitinview $line $curview]} continue
3796 set row [rowofcommit $line]
3797 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
3798 bolden $row mainfontbold
3800 set fhighlights($line) 1
3802 if {[eof $filehighlight]} {
3803 # strange...
3804 puts "oops, git diff-tree died"
3805 catch {close $filehighlight}
3806 unset filehighlight
3807 return 0
3809 if {[info exists find_dirn]} {
3810 run findmore
3812 return 1
3815 proc doesmatch {f} {
3816 global findtype findpattern
3818 if {$findtype eq [mc "Regexp"]} {
3819 return [regexp $findpattern $f]
3820 } elseif {$findtype eq [mc "IgnCase"]} {
3821 return [string match -nocase $findpattern $f]
3822 } else {
3823 return [string match $findpattern $f]
3827 proc askfindhighlight {row id} {
3828 global nhighlights commitinfo iddrawn
3829 global findloc
3830 global markingmatches
3832 if {![info exists commitinfo($id)]} {
3833 getcommit $id
3835 set info $commitinfo($id)
3836 set isbold 0
3837 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
3838 foreach f $info ty $fldtypes {
3839 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
3840 [doesmatch $f]} {
3841 if {$ty eq [mc "Author"]} {
3842 set isbold 2
3843 break
3845 set isbold 1
3848 if {$isbold && [info exists iddrawn($id)]} {
3849 if {![ishighlighted $id]} {
3850 bolden $row mainfontbold
3851 if {$isbold > 1} {
3852 bolden_name $row mainfontbold
3855 if {$markingmatches} {
3856 markrowmatches $row $id
3859 set nhighlights($id) $isbold
3862 proc markrowmatches {row id} {
3863 global canv canv2 linehtag linentag commitinfo findloc
3865 set headline [lindex $commitinfo($id) 0]
3866 set author [lindex $commitinfo($id) 1]
3867 $canv delete match$row
3868 $canv2 delete match$row
3869 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
3870 set m [findmatches $headline]
3871 if {$m ne {}} {
3872 markmatches $canv $row $headline $linehtag($row) $m \
3873 [$canv itemcget $linehtag($row) -font] $row
3876 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
3877 set m [findmatches $author]
3878 if {$m ne {}} {
3879 markmatches $canv2 $row $author $linentag($row) $m \
3880 [$canv2 itemcget $linentag($row) -font] $row
3885 proc vrel_change {name ix op} {
3886 global highlight_related
3888 rhighlight_none
3889 if {$highlight_related ne [mc "None"]} {
3890 run drawvisible
3894 # prepare for testing whether commits are descendents or ancestors of a
3895 proc rhighlight_sel {a} {
3896 global descendent desc_todo ancestor anc_todo
3897 global highlight_related
3899 catch {unset descendent}
3900 set desc_todo [list $a]
3901 catch {unset ancestor}
3902 set anc_todo [list $a]
3903 if {$highlight_related ne [mc "None"]} {
3904 rhighlight_none
3905 run drawvisible
3909 proc rhighlight_none {} {
3910 global rhighlights
3912 catch {unset rhighlights}
3913 unbolden
3916 proc is_descendent {a} {
3917 global curview children descendent desc_todo
3919 set v $curview
3920 set la [rowofcommit $a]
3921 set todo $desc_todo
3922 set leftover {}
3923 set done 0
3924 for {set i 0} {$i < [llength $todo]} {incr i} {
3925 set do [lindex $todo $i]
3926 if {[rowofcommit $do] < $la} {
3927 lappend leftover $do
3928 continue
3930 foreach nk $children($v,$do) {
3931 if {![info exists descendent($nk)]} {
3932 set descendent($nk) 1
3933 lappend todo $nk
3934 if {$nk eq $a} {
3935 set done 1
3939 if {$done} {
3940 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3941 return
3944 set descendent($a) 0
3945 set desc_todo $leftover
3948 proc is_ancestor {a} {
3949 global curview parents ancestor anc_todo
3951 set v $curview
3952 set la [rowofcommit $a]
3953 set todo $anc_todo
3954 set leftover {}
3955 set done 0
3956 for {set i 0} {$i < [llength $todo]} {incr i} {
3957 set do [lindex $todo $i]
3958 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
3959 lappend leftover $do
3960 continue
3962 foreach np $parents($v,$do) {
3963 if {![info exists ancestor($np)]} {
3964 set ancestor($np) 1
3965 lappend todo $np
3966 if {$np eq $a} {
3967 set done 1
3971 if {$done} {
3972 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3973 return
3976 set ancestor($a) 0
3977 set anc_todo $leftover
3980 proc askrelhighlight {row id} {
3981 global descendent highlight_related iddrawn rhighlights
3982 global selectedline ancestor
3984 if {$selectedline eq {}} return
3985 set isbold 0
3986 if {$highlight_related eq [mc "Descendant"] ||
3987 $highlight_related eq [mc "Not descendant"]} {
3988 if {![info exists descendent($id)]} {
3989 is_descendent $id
3991 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
3992 set isbold 1
3994 } elseif {$highlight_related eq [mc "Ancestor"] ||
3995 $highlight_related eq [mc "Not ancestor"]} {
3996 if {![info exists ancestor($id)]} {
3997 is_ancestor $id
3999 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4000 set isbold 1
4003 if {[info exists iddrawn($id)]} {
4004 if {$isbold && ![ishighlighted $id]} {
4005 bolden $row mainfontbold
4008 set rhighlights($id) $isbold
4011 # Graph layout functions
4013 proc shortids {ids} {
4014 set res {}
4015 foreach id $ids {
4016 if {[llength $id] > 1} {
4017 lappend res [shortids $id]
4018 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4019 lappend res [string range $id 0 7]
4020 } else {
4021 lappend res $id
4024 return $res
4027 proc ntimes {n o} {
4028 set ret {}
4029 set o [list $o]
4030 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4031 if {($n & $mask) != 0} {
4032 set ret [concat $ret $o]
4034 set o [concat $o $o]
4036 return $ret
4039 proc ordertoken {id} {
4040 global ordertok curview varcid varcstart varctok curview parents children
4041 global nullid nullid2
4043 if {[info exists ordertok($id)]} {
4044 return $ordertok($id)
4046 set origid $id
4047 set todo {}
4048 while {1} {
4049 if {[info exists varcid($curview,$id)]} {
4050 set a $varcid($curview,$id)
4051 set p [lindex $varcstart($curview) $a]
4052 } else {
4053 set p [lindex $children($curview,$id) 0]
4055 if {[info exists ordertok($p)]} {
4056 set tok $ordertok($p)
4057 break
4059 set id [first_real_child $curview,$p]
4060 if {$id eq {}} {
4061 # it's a root
4062 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4063 break
4065 if {[llength $parents($curview,$id)] == 1} {
4066 lappend todo [list $p {}]
4067 } else {
4068 set j [lsearch -exact $parents($curview,$id) $p]
4069 if {$j < 0} {
4070 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4072 lappend todo [list $p [strrep $j]]
4075 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4076 set p [lindex $todo $i 0]
4077 append tok [lindex $todo $i 1]
4078 set ordertok($p) $tok
4080 set ordertok($origid) $tok
4081 return $tok
4084 # Work out where id should go in idlist so that order-token
4085 # values increase from left to right
4086 proc idcol {idlist id {i 0}} {
4087 set t [ordertoken $id]
4088 if {$i < 0} {
4089 set i 0
4091 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4092 if {$i > [llength $idlist]} {
4093 set i [llength $idlist]
4095 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4096 incr i
4097 } else {
4098 if {$t > [ordertoken [lindex $idlist $i]]} {
4099 while {[incr i] < [llength $idlist] &&
4100 $t >= [ordertoken [lindex $idlist $i]]} {}
4103 return $i
4106 proc initlayout {} {
4107 global rowidlist rowisopt rowfinal displayorder parentlist
4108 global numcommits canvxmax canv
4109 global nextcolor
4110 global colormap rowtextx
4112 set numcommits 0
4113 set displayorder {}
4114 set parentlist {}
4115 set nextcolor 0
4116 set rowidlist {}
4117 set rowisopt {}
4118 set rowfinal {}
4119 set canvxmax [$canv cget -width]
4120 catch {unset colormap}
4121 catch {unset rowtextx}
4122 setcanvscroll
4125 proc setcanvscroll {} {
4126 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4127 global lastscrollset lastscrollrows
4129 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4130 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4131 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4132 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4133 set lastscrollset [clock clicks -milliseconds]
4134 set lastscrollrows $numcommits
4137 proc visiblerows {} {
4138 global canv numcommits linespc
4140 set ymax [lindex [$canv cget -scrollregion] 3]
4141 if {$ymax eq {} || $ymax == 0} return
4142 set f [$canv yview]
4143 set y0 [expr {int([lindex $f 0] * $ymax)}]
4144 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4145 if {$r0 < 0} {
4146 set r0 0
4148 set y1 [expr {int([lindex $f 1] * $ymax)}]
4149 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4150 if {$r1 >= $numcommits} {
4151 set r1 [expr {$numcommits - 1}]
4153 return [list $r0 $r1]
4156 proc layoutmore {} {
4157 global commitidx viewcomplete curview
4158 global numcommits pending_select curview
4159 global lastscrollset lastscrollrows
4161 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4162 [clock clicks -milliseconds] - $lastscrollset > 500} {
4163 setcanvscroll
4165 if {[info exists pending_select] &&
4166 [commitinview $pending_select $curview]} {
4167 update
4168 selectline [rowofcommit $pending_select] 1
4170 drawvisible
4173 proc doshowlocalchanges {} {
4174 global curview mainheadid
4176 if {$mainheadid eq {}} return
4177 if {[commitinview $mainheadid $curview]} {
4178 dodiffindex
4179 } else {
4180 interestedin $mainheadid dodiffindex
4184 proc dohidelocalchanges {} {
4185 global nullid nullid2 lserial curview
4187 if {[commitinview $nullid $curview]} {
4188 removefakerow $nullid
4190 if {[commitinview $nullid2 $curview]} {
4191 removefakerow $nullid2
4193 incr lserial
4196 # spawn off a process to do git diff-index --cached HEAD
4197 proc dodiffindex {} {
4198 global lserial showlocalchanges
4199 global isworktree
4201 if {!$showlocalchanges || !$isworktree} return
4202 incr lserial
4203 set fd [open "|git diff-index --cached HEAD" r]
4204 fconfigure $fd -blocking 0
4205 set i [reg_instance $fd]
4206 filerun $fd [list readdiffindex $fd $lserial $i]
4209 proc readdiffindex {fd serial inst} {
4210 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4212 set isdiff 1
4213 if {[gets $fd line] < 0} {
4214 if {![eof $fd]} {
4215 return 1
4217 set isdiff 0
4219 # we only need to see one line and we don't really care what it says...
4220 stop_instance $inst
4222 if {$serial != $lserial} {
4223 return 0
4226 # now see if there are any local changes not checked in to the index
4227 set fd [open "|git diff-files" r]
4228 fconfigure $fd -blocking 0
4229 set i [reg_instance $fd]
4230 filerun $fd [list readdifffiles $fd $serial $i]
4232 if {$isdiff && ![commitinview $nullid2 $curview]} {
4233 # add the line for the changes in the index to the graph
4234 set hl [mc "Local changes checked in to index but not committed"]
4235 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4236 set commitdata($nullid2) "\n $hl\n"
4237 if {[commitinview $nullid $curview]} {
4238 removefakerow $nullid
4240 insertfakerow $nullid2 $mainheadid
4241 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4242 removefakerow $nullid2
4244 return 0
4247 proc readdifffiles {fd serial inst} {
4248 global mainheadid nullid nullid2 curview
4249 global commitinfo commitdata lserial
4251 set isdiff 1
4252 if {[gets $fd line] < 0} {
4253 if {![eof $fd]} {
4254 return 1
4256 set isdiff 0
4258 # we only need to see one line and we don't really care what it says...
4259 stop_instance $inst
4261 if {$serial != $lserial} {
4262 return 0
4265 if {$isdiff && ![commitinview $nullid $curview]} {
4266 # add the line for the local diff to the graph
4267 set hl [mc "Local uncommitted changes, not checked in to index"]
4268 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4269 set commitdata($nullid) "\n $hl\n"
4270 if {[commitinview $nullid2 $curview]} {
4271 set p $nullid2
4272 } else {
4273 set p $mainheadid
4275 insertfakerow $nullid $p
4276 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4277 removefakerow $nullid
4279 return 0
4282 proc nextuse {id row} {
4283 global curview children
4285 if {[info exists children($curview,$id)]} {
4286 foreach kid $children($curview,$id) {
4287 if {![commitinview $kid $curview]} {
4288 return -1
4290 if {[rowofcommit $kid] > $row} {
4291 return [rowofcommit $kid]
4295 if {[commitinview $id $curview]} {
4296 return [rowofcommit $id]
4298 return -1
4301 proc prevuse {id row} {
4302 global curview children
4304 set ret -1
4305 if {[info exists children($curview,$id)]} {
4306 foreach kid $children($curview,$id) {
4307 if {![commitinview $kid $curview]} break
4308 if {[rowofcommit $kid] < $row} {
4309 set ret [rowofcommit $kid]
4313 return $ret
4316 proc make_idlist {row} {
4317 global displayorder parentlist uparrowlen downarrowlen mingaplen
4318 global commitidx curview children
4320 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4321 if {$r < 0} {
4322 set r 0
4324 set ra [expr {$row - $downarrowlen}]
4325 if {$ra < 0} {
4326 set ra 0
4328 set rb [expr {$row + $uparrowlen}]
4329 if {$rb > $commitidx($curview)} {
4330 set rb $commitidx($curview)
4332 make_disporder $r [expr {$rb + 1}]
4333 set ids {}
4334 for {} {$r < $ra} {incr r} {
4335 set nextid [lindex $displayorder [expr {$r + 1}]]
4336 foreach p [lindex $parentlist $r] {
4337 if {$p eq $nextid} continue
4338 set rn [nextuse $p $r]
4339 if {$rn >= $row &&
4340 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4341 lappend ids [list [ordertoken $p] $p]
4345 for {} {$r < $row} {incr r} {
4346 set nextid [lindex $displayorder [expr {$r + 1}]]
4347 foreach p [lindex $parentlist $r] {
4348 if {$p eq $nextid} continue
4349 set rn [nextuse $p $r]
4350 if {$rn < 0 || $rn >= $row} {
4351 lappend ids [list [ordertoken $p] $p]
4355 set id [lindex $displayorder $row]
4356 lappend ids [list [ordertoken $id] $id]
4357 while {$r < $rb} {
4358 foreach p [lindex $parentlist $r] {
4359 set firstkid [lindex $children($curview,$p) 0]
4360 if {[rowofcommit $firstkid] < $row} {
4361 lappend ids [list [ordertoken $p] $p]
4364 incr r
4365 set id [lindex $displayorder $r]
4366 if {$id ne {}} {
4367 set firstkid [lindex $children($curview,$id) 0]
4368 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4369 lappend ids [list [ordertoken $id] $id]
4373 set idlist {}
4374 foreach idx [lsort -unique $ids] {
4375 lappend idlist [lindex $idx 1]
4377 return $idlist
4380 proc rowsequal {a b} {
4381 while {[set i [lsearch -exact $a {}]] >= 0} {
4382 set a [lreplace $a $i $i]
4384 while {[set i [lsearch -exact $b {}]] >= 0} {
4385 set b [lreplace $b $i $i]
4387 return [expr {$a eq $b}]
4390 proc makeupline {id row rend col} {
4391 global rowidlist uparrowlen downarrowlen mingaplen
4393 for {set r $rend} {1} {set r $rstart} {
4394 set rstart [prevuse $id $r]
4395 if {$rstart < 0} return
4396 if {$rstart < $row} break
4398 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4399 set rstart [expr {$rend - $uparrowlen - 1}]
4401 for {set r $rstart} {[incr r] <= $row} {} {
4402 set idlist [lindex $rowidlist $r]
4403 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4404 set col [idcol $idlist $id $col]
4405 lset rowidlist $r [linsert $idlist $col $id]
4406 changedrow $r
4411 proc layoutrows {row endrow} {
4412 global rowidlist rowisopt rowfinal displayorder
4413 global uparrowlen downarrowlen maxwidth mingaplen
4414 global children parentlist
4415 global commitidx viewcomplete curview
4417 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4418 set idlist {}
4419 if {$row > 0} {
4420 set rm1 [expr {$row - 1}]
4421 foreach id [lindex $rowidlist $rm1] {
4422 if {$id ne {}} {
4423 lappend idlist $id
4426 set final [lindex $rowfinal $rm1]
4428 for {} {$row < $endrow} {incr row} {
4429 set rm1 [expr {$row - 1}]
4430 if {$rm1 < 0 || $idlist eq {}} {
4431 set idlist [make_idlist $row]
4432 set final 1
4433 } else {
4434 set id [lindex $displayorder $rm1]
4435 set col [lsearch -exact $idlist $id]
4436 set idlist [lreplace $idlist $col $col]
4437 foreach p [lindex $parentlist $rm1] {
4438 if {[lsearch -exact $idlist $p] < 0} {
4439 set col [idcol $idlist $p $col]
4440 set idlist [linsert $idlist $col $p]
4441 # if not the first child, we have to insert a line going up
4442 if {$id ne [lindex $children($curview,$p) 0]} {
4443 makeupline $p $rm1 $row $col
4447 set id [lindex $displayorder $row]
4448 if {$row > $downarrowlen} {
4449 set termrow [expr {$row - $downarrowlen - 1}]
4450 foreach p [lindex $parentlist $termrow] {
4451 set i [lsearch -exact $idlist $p]
4452 if {$i < 0} continue
4453 set nr [nextuse $p $termrow]
4454 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4455 set idlist [lreplace $idlist $i $i]
4459 set col [lsearch -exact $idlist $id]
4460 if {$col < 0} {
4461 set col [idcol $idlist $id]
4462 set idlist [linsert $idlist $col $id]
4463 if {$children($curview,$id) ne {}} {
4464 makeupline $id $rm1 $row $col
4467 set r [expr {$row + $uparrowlen - 1}]
4468 if {$r < $commitidx($curview)} {
4469 set x $col
4470 foreach p [lindex $parentlist $r] {
4471 if {[lsearch -exact $idlist $p] >= 0} continue
4472 set fk [lindex $children($curview,$p) 0]
4473 if {[rowofcommit $fk] < $row} {
4474 set x [idcol $idlist $p $x]
4475 set idlist [linsert $idlist $x $p]
4478 if {[incr r] < $commitidx($curview)} {
4479 set p [lindex $displayorder $r]
4480 if {[lsearch -exact $idlist $p] < 0} {
4481 set fk [lindex $children($curview,$p) 0]
4482 if {$fk ne {} && [rowofcommit $fk] < $row} {
4483 set x [idcol $idlist $p $x]
4484 set idlist [linsert $idlist $x $p]
4490 if {$final && !$viewcomplete($curview) &&
4491 $row + $uparrowlen + $mingaplen + $downarrowlen
4492 >= $commitidx($curview)} {
4493 set final 0
4495 set l [llength $rowidlist]
4496 if {$row == $l} {
4497 lappend rowidlist $idlist
4498 lappend rowisopt 0
4499 lappend rowfinal $final
4500 } elseif {$row < $l} {
4501 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4502 lset rowidlist $row $idlist
4503 changedrow $row
4505 lset rowfinal $row $final
4506 } else {
4507 set pad [ntimes [expr {$row - $l}] {}]
4508 set rowidlist [concat $rowidlist $pad]
4509 lappend rowidlist $idlist
4510 set rowfinal [concat $rowfinal $pad]
4511 lappend rowfinal $final
4512 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4515 return $row
4518 proc changedrow {row} {
4519 global displayorder iddrawn rowisopt need_redisplay
4521 set l [llength $rowisopt]
4522 if {$row < $l} {
4523 lset rowisopt $row 0
4524 if {$row + 1 < $l} {
4525 lset rowisopt [expr {$row + 1}] 0
4526 if {$row + 2 < $l} {
4527 lset rowisopt [expr {$row + 2}] 0
4531 set id [lindex $displayorder $row]
4532 if {[info exists iddrawn($id)]} {
4533 set need_redisplay 1
4537 proc insert_pad {row col npad} {
4538 global rowidlist
4540 set pad [ntimes $npad {}]
4541 set idlist [lindex $rowidlist $row]
4542 set bef [lrange $idlist 0 [expr {$col - 1}]]
4543 set aft [lrange $idlist $col end]
4544 set i [lsearch -exact $aft {}]
4545 if {$i > 0} {
4546 set aft [lreplace $aft $i $i]
4548 lset rowidlist $row [concat $bef $pad $aft]
4549 changedrow $row
4552 proc optimize_rows {row col endrow} {
4553 global rowidlist rowisopt displayorder curview children
4555 if {$row < 1} {
4556 set row 1
4558 for {} {$row < $endrow} {incr row; set col 0} {
4559 if {[lindex $rowisopt $row]} continue
4560 set haspad 0
4561 set y0 [expr {$row - 1}]
4562 set ym [expr {$row - 2}]
4563 set idlist [lindex $rowidlist $row]
4564 set previdlist [lindex $rowidlist $y0]
4565 if {$idlist eq {} || $previdlist eq {}} continue
4566 if {$ym >= 0} {
4567 set pprevidlist [lindex $rowidlist $ym]
4568 if {$pprevidlist eq {}} continue
4569 } else {
4570 set pprevidlist {}
4572 set x0 -1
4573 set xm -1
4574 for {} {$col < [llength $idlist]} {incr col} {
4575 set id [lindex $idlist $col]
4576 if {[lindex $previdlist $col] eq $id} continue
4577 if {$id eq {}} {
4578 set haspad 1
4579 continue
4581 set x0 [lsearch -exact $previdlist $id]
4582 if {$x0 < 0} continue
4583 set z [expr {$x0 - $col}]
4584 set isarrow 0
4585 set z0 {}
4586 if {$ym >= 0} {
4587 set xm [lsearch -exact $pprevidlist $id]
4588 if {$xm >= 0} {
4589 set z0 [expr {$xm - $x0}]
4592 if {$z0 eq {}} {
4593 # if row y0 is the first child of $id then it's not an arrow
4594 if {[lindex $children($curview,$id) 0] ne
4595 [lindex $displayorder $y0]} {
4596 set isarrow 1
4599 if {!$isarrow && $id ne [lindex $displayorder $row] &&
4600 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
4601 set isarrow 1
4603 # Looking at lines from this row to the previous row,
4604 # make them go straight up if they end in an arrow on
4605 # the previous row; otherwise make them go straight up
4606 # or at 45 degrees.
4607 if {$z < -1 || ($z < 0 && $isarrow)} {
4608 # Line currently goes left too much;
4609 # insert pads in the previous row, then optimize it
4610 set npad [expr {-1 - $z + $isarrow}]
4611 insert_pad $y0 $x0 $npad
4612 if {$y0 > 0} {
4613 optimize_rows $y0 $x0 $row
4615 set previdlist [lindex $rowidlist $y0]
4616 set x0 [lsearch -exact $previdlist $id]
4617 set z [expr {$x0 - $col}]
4618 if {$z0 ne {}} {
4619 set pprevidlist [lindex $rowidlist $ym]
4620 set xm [lsearch -exact $pprevidlist $id]
4621 set z0 [expr {$xm - $x0}]
4623 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
4624 # Line currently goes right too much;
4625 # insert pads in this line
4626 set npad [expr {$z - 1 + $isarrow}]
4627 insert_pad $row $col $npad
4628 set idlist [lindex $rowidlist $row]
4629 incr col $npad
4630 set z [expr {$x0 - $col}]
4631 set haspad 1
4633 if {$z0 eq {} && !$isarrow && $ym >= 0} {
4634 # this line links to its first child on row $row-2
4635 set id [lindex $displayorder $ym]
4636 set xc [lsearch -exact $pprevidlist $id]
4637 if {$xc >= 0} {
4638 set z0 [expr {$xc - $x0}]
4641 # avoid lines jigging left then immediately right
4642 if {$z0 ne {} && $z < 0 && $z0 > 0} {
4643 insert_pad $y0 $x0 1
4644 incr x0
4645 optimize_rows $y0 $x0 $row
4646 set previdlist [lindex $rowidlist $y0]
4649 if {!$haspad} {
4650 # Find the first column that doesn't have a line going right
4651 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
4652 set id [lindex $idlist $col]
4653 if {$id eq {}} break
4654 set x0 [lsearch -exact $previdlist $id]
4655 if {$x0 < 0} {
4656 # check if this is the link to the first child
4657 set kid [lindex $displayorder $y0]
4658 if {[lindex $children($curview,$id) 0] eq $kid} {
4659 # it is, work out offset to child
4660 set x0 [lsearch -exact $previdlist $kid]
4663 if {$x0 <= $col} break
4665 # Insert a pad at that column as long as it has a line and
4666 # isn't the last column
4667 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
4668 set idlist [linsert $idlist $col {}]
4669 lset rowidlist $row $idlist
4670 changedrow $row
4676 proc xc {row col} {
4677 global canvx0 linespc
4678 return [expr {$canvx0 + $col * $linespc}]
4681 proc yc {row} {
4682 global canvy0 linespc
4683 return [expr {$canvy0 + $row * $linespc}]
4686 proc linewidth {id} {
4687 global thickerline lthickness
4689 set wid $lthickness
4690 if {[info exists thickerline] && $id eq $thickerline} {
4691 set wid [expr {2 * $lthickness}]
4693 return $wid
4696 proc rowranges {id} {
4697 global curview children uparrowlen downarrowlen
4698 global rowidlist
4700 set kids $children($curview,$id)
4701 if {$kids eq {}} {
4702 return {}
4704 set ret {}
4705 lappend kids $id
4706 foreach child $kids {
4707 if {![commitinview $child $curview]} break
4708 set row [rowofcommit $child]
4709 if {![info exists prev]} {
4710 lappend ret [expr {$row + 1}]
4711 } else {
4712 if {$row <= $prevrow} {
4713 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4715 # see if the line extends the whole way from prevrow to row
4716 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4717 [lsearch -exact [lindex $rowidlist \
4718 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4719 # it doesn't, see where it ends
4720 set r [expr {$prevrow + $downarrowlen}]
4721 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4722 while {[incr r -1] > $prevrow &&
4723 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4724 } else {
4725 while {[incr r] <= $row &&
4726 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4727 incr r -1
4729 lappend ret $r
4730 # see where it starts up again
4731 set r [expr {$row - $uparrowlen}]
4732 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4733 while {[incr r] < $row &&
4734 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4735 } else {
4736 while {[incr r -1] >= $prevrow &&
4737 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4738 incr r
4740 lappend ret $r
4743 if {$child eq $id} {
4744 lappend ret $row
4746 set prev $child
4747 set prevrow $row
4749 return $ret
4752 proc drawlineseg {id row endrow arrowlow} {
4753 global rowidlist displayorder iddrawn linesegs
4754 global canv colormap linespc curview maxlinelen parentlist
4756 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4757 set le [expr {$row + 1}]
4758 set arrowhigh 1
4759 while {1} {
4760 set c [lsearch -exact [lindex $rowidlist $le] $id]
4761 if {$c < 0} {
4762 incr le -1
4763 break
4765 lappend cols $c
4766 set x [lindex $displayorder $le]
4767 if {$x eq $id} {
4768 set arrowhigh 0
4769 break
4771 if {[info exists iddrawn($x)] || $le == $endrow} {
4772 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
4773 if {$c >= 0} {
4774 lappend cols $c
4775 set arrowhigh 0
4777 break
4779 incr le
4781 if {$le <= $row} {
4782 return $row
4785 set lines {}
4786 set i 0
4787 set joinhigh 0
4788 if {[info exists linesegs($id)]} {
4789 set lines $linesegs($id)
4790 foreach li $lines {
4791 set r0 [lindex $li 0]
4792 if {$r0 > $row} {
4793 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
4794 set joinhigh 1
4796 break
4798 incr i
4801 set joinlow 0
4802 if {$i > 0} {
4803 set li [lindex $lines [expr {$i-1}]]
4804 set r1 [lindex $li 1]
4805 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
4806 set joinlow 1
4810 set x [lindex $cols [expr {$le - $row}]]
4811 set xp [lindex $cols [expr {$le - 1 - $row}]]
4812 set dir [expr {$xp - $x}]
4813 if {$joinhigh} {
4814 set ith [lindex $lines $i 2]
4815 set coords [$canv coords $ith]
4816 set ah [$canv itemcget $ith -arrow]
4817 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
4818 set x2 [lindex $cols [expr {$le + 1 - $row}]]
4819 if {$x2 ne {} && $x - $x2 == $dir} {
4820 set coords [lrange $coords 0 end-2]
4822 } else {
4823 set coords [list [xc $le $x] [yc $le]]
4825 if {$joinlow} {
4826 set itl [lindex $lines [expr {$i-1}] 2]
4827 set al [$canv itemcget $itl -arrow]
4828 set arrowlow [expr {$al eq "last" || $al eq "both"}]
4829 } elseif {$arrowlow} {
4830 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
4831 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
4832 set arrowlow 0
4835 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
4836 for {set y $le} {[incr y -1] > $row} {} {
4837 set x $xp
4838 set xp [lindex $cols [expr {$y - 1 - $row}]]
4839 set ndir [expr {$xp - $x}]
4840 if {$dir != $ndir || $xp < 0} {
4841 lappend coords [xc $y $x] [yc $y]
4843 set dir $ndir
4845 if {!$joinlow} {
4846 if {$xp < 0} {
4847 # join parent line to first child
4848 set ch [lindex $displayorder $row]
4849 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
4850 if {$xc < 0} {
4851 puts "oops: drawlineseg: child $ch not on row $row"
4852 } elseif {$xc != $x} {
4853 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
4854 set d [expr {int(0.5 * $linespc)}]
4855 set x1 [xc $row $x]
4856 if {$xc < $x} {
4857 set x2 [expr {$x1 - $d}]
4858 } else {
4859 set x2 [expr {$x1 + $d}]
4861 set y2 [yc $row]
4862 set y1 [expr {$y2 + $d}]
4863 lappend coords $x1 $y1 $x2 $y2
4864 } elseif {$xc < $x - 1} {
4865 lappend coords [xc $row [expr {$x-1}]] [yc $row]
4866 } elseif {$xc > $x + 1} {
4867 lappend coords [xc $row [expr {$x+1}]] [yc $row]
4869 set x $xc
4871 lappend coords [xc $row $x] [yc $row]
4872 } else {
4873 set xn [xc $row $xp]
4874 set yn [yc $row]
4875 lappend coords $xn $yn
4877 if {!$joinhigh} {
4878 assigncolor $id
4879 set t [$canv create line $coords -width [linewidth $id] \
4880 -fill $colormap($id) -tags lines.$id -arrow $arrow]
4881 $canv lower $t
4882 bindline $t $id
4883 set lines [linsert $lines $i [list $row $le $t]]
4884 } else {
4885 $canv coords $ith $coords
4886 if {$arrow ne $ah} {
4887 $canv itemconf $ith -arrow $arrow
4889 lset lines $i 0 $row
4891 } else {
4892 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
4893 set ndir [expr {$xo - $xp}]
4894 set clow [$canv coords $itl]
4895 if {$dir == $ndir} {
4896 set clow [lrange $clow 2 end]
4898 set coords [concat $coords $clow]
4899 if {!$joinhigh} {
4900 lset lines [expr {$i-1}] 1 $le
4901 } else {
4902 # coalesce two pieces
4903 $canv delete $ith
4904 set b [lindex $lines [expr {$i-1}] 0]
4905 set e [lindex $lines $i 1]
4906 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
4908 $canv coords $itl $coords
4909 if {$arrow ne $al} {
4910 $canv itemconf $itl -arrow $arrow
4914 set linesegs($id) $lines
4915 return $le
4918 proc drawparentlinks {id row} {
4919 global rowidlist canv colormap curview parentlist
4920 global idpos linespc
4922 set rowids [lindex $rowidlist $row]
4923 set col [lsearch -exact $rowids $id]
4924 if {$col < 0} return
4925 set olds [lindex $parentlist $row]
4926 set row2 [expr {$row + 1}]
4927 set x [xc $row $col]
4928 set y [yc $row]
4929 set y2 [yc $row2]
4930 set d [expr {int(0.5 * $linespc)}]
4931 set ymid [expr {$y + $d}]
4932 set ids [lindex $rowidlist $row2]
4933 # rmx = right-most X coord used
4934 set rmx 0
4935 foreach p $olds {
4936 set i [lsearch -exact $ids $p]
4937 if {$i < 0} {
4938 puts "oops, parent $p of $id not in list"
4939 continue
4941 set x2 [xc $row2 $i]
4942 if {$x2 > $rmx} {
4943 set rmx $x2
4945 set j [lsearch -exact $rowids $p]
4946 if {$j < 0} {
4947 # drawlineseg will do this one for us
4948 continue
4950 assigncolor $p
4951 # should handle duplicated parents here...
4952 set coords [list $x $y]
4953 if {$i != $col} {
4954 # if attaching to a vertical segment, draw a smaller
4955 # slant for visual distinctness
4956 if {$i == $j} {
4957 if {$i < $col} {
4958 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
4959 } else {
4960 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
4962 } elseif {$i < $col && $i < $j} {
4963 # segment slants towards us already
4964 lappend coords [xc $row $j] $y
4965 } else {
4966 if {$i < $col - 1} {
4967 lappend coords [expr {$x2 + $linespc}] $y
4968 } elseif {$i > $col + 1} {
4969 lappend coords [expr {$x2 - $linespc}] $y
4971 lappend coords $x2 $y2
4973 } else {
4974 lappend coords $x2 $y2
4976 set t [$canv create line $coords -width [linewidth $p] \
4977 -fill $colormap($p) -tags lines.$p]
4978 $canv lower $t
4979 bindline $t $p
4981 if {$rmx > [lindex $idpos($id) 1]} {
4982 lset idpos($id) 1 $rmx
4983 redrawtags $id
4987 proc drawlines {id} {
4988 global canv
4990 $canv itemconf lines.$id -width [linewidth $id]
4993 proc drawcmittext {id row col} {
4994 global linespc canv canv2 canv3 fgcolor curview
4995 global cmitlisted commitinfo rowidlist parentlist
4996 global rowtextx idpos idtags idheads idotherrefs
4997 global linehtag linentag linedtag selectedline
4998 global canvxmax boldrows boldnamerows fgcolor
4999 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5001 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5002 set listed $cmitlisted($curview,$id)
5003 if {$id eq $nullid} {
5004 set ofill red
5005 } elseif {$id eq $nullid2} {
5006 set ofill green
5007 } elseif {$id eq $mainheadid} {
5008 set ofill yellow
5009 } else {
5010 set ofill [lindex $circlecolors $listed]
5012 set x [xc $row $col]
5013 set y [yc $row]
5014 set orad [expr {$linespc / 3}]
5015 if {$listed <= 2} {
5016 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5017 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5018 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5019 } elseif {$listed == 3} {
5020 # triangle pointing left for left-side commits
5021 set t [$canv create polygon \
5022 [expr {$x - $orad}] $y \
5023 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5024 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5025 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5026 } else {
5027 # triangle pointing right for right-side commits
5028 set t [$canv create polygon \
5029 [expr {$x + $orad - 1}] $y \
5030 [expr {$x - $orad}] [expr {$y - $orad}] \
5031 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5032 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5034 set circleitem($row) $t
5035 $canv raise $t
5036 $canv bind $t <1> {selcanvline {} %x %y}
5037 set rmx [llength [lindex $rowidlist $row]]
5038 set olds [lindex $parentlist $row]
5039 if {$olds ne {}} {
5040 set nextids [lindex $rowidlist [expr {$row + 1}]]
5041 foreach p $olds {
5042 set i [lsearch -exact $nextids $p]
5043 if {$i > $rmx} {
5044 set rmx $i
5048 set xt [xc $row $rmx]
5049 set rowtextx($row) $xt
5050 set idpos($id) [list $x $xt $y]
5051 if {[info exists idtags($id)] || [info exists idheads($id)]
5052 || [info exists idotherrefs($id)]} {
5053 set xt [drawtags $id $x $xt $y]
5055 set headline [lindex $commitinfo($id) 0]
5056 set name [lindex $commitinfo($id) 1]
5057 set date [lindex $commitinfo($id) 2]
5058 set date [formatdate $date]
5059 set font mainfont
5060 set nfont mainfont
5061 set isbold [ishighlighted $id]
5062 if {$isbold > 0} {
5063 lappend boldrows $row
5064 set font mainfontbold
5065 if {$isbold > 1} {
5066 lappend boldnamerows $row
5067 set nfont mainfontbold
5070 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
5071 -text $headline -font $font -tags text]
5072 $canv bind $linehtag($row) $ctxbut "rowmenu %X %Y $id"
5073 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5074 -text $name -font $nfont -tags text]
5075 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5076 -text $date -font mainfont -tags text]
5077 if {$selectedline == $row} {
5078 make_secsel $row
5080 set xr [expr {$xt + [font measure $font $headline]}]
5081 if {$xr > $canvxmax} {
5082 set canvxmax $xr
5083 setcanvscroll
5087 proc drawcmitrow {row} {
5088 global displayorder rowidlist nrows_drawn
5089 global iddrawn markingmatches
5090 global commitinfo numcommits
5091 global filehighlight fhighlights findpattern nhighlights
5092 global hlview vhighlights
5093 global highlight_related rhighlights
5095 if {$row >= $numcommits} return
5097 set id [lindex $displayorder $row]
5098 if {[info exists hlview] && ![info exists vhighlights($id)]} {
5099 askvhighlight $row $id
5101 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5102 askfilehighlight $row $id
5104 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5105 askfindhighlight $row $id
5107 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5108 askrelhighlight $row $id
5110 if {![info exists iddrawn($id)]} {
5111 set col [lsearch -exact [lindex $rowidlist $row] $id]
5112 if {$col < 0} {
5113 puts "oops, row $row id $id not in list"
5114 return
5116 if {![info exists commitinfo($id)]} {
5117 getcommit $id
5119 assigncolor $id
5120 drawcmittext $id $row $col
5121 set iddrawn($id) 1
5122 incr nrows_drawn
5124 if {$markingmatches} {
5125 markrowmatches $row $id
5129 proc drawcommits {row {endrow {}}} {
5130 global numcommits iddrawn displayorder curview need_redisplay
5131 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5133 if {$row < 0} {
5134 set row 0
5136 if {$endrow eq {}} {
5137 set endrow $row
5139 if {$endrow >= $numcommits} {
5140 set endrow [expr {$numcommits - 1}]
5143 set rl1 [expr {$row - $downarrowlen - 3}]
5144 if {$rl1 < 0} {
5145 set rl1 0
5147 set ro1 [expr {$row - 3}]
5148 if {$ro1 < 0} {
5149 set ro1 0
5151 set r2 [expr {$endrow + $uparrowlen + 3}]
5152 if {$r2 > $numcommits} {
5153 set r2 $numcommits
5155 for {set r $rl1} {$r < $r2} {incr r} {
5156 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5157 if {$rl1 < $r} {
5158 layoutrows $rl1 $r
5160 set rl1 [expr {$r + 1}]
5163 if {$rl1 < $r} {
5164 layoutrows $rl1 $r
5166 optimize_rows $ro1 0 $r2
5167 if {$need_redisplay || $nrows_drawn > 2000} {
5168 clear_display
5169 drawvisible
5172 # make the lines join to already-drawn rows either side
5173 set r [expr {$row - 1}]
5174 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5175 set r $row
5177 set er [expr {$endrow + 1}]
5178 if {$er >= $numcommits ||
5179 ![info exists iddrawn([lindex $displayorder $er])]} {
5180 set er $endrow
5182 for {} {$r <= $er} {incr r} {
5183 set id [lindex $displayorder $r]
5184 set wasdrawn [info exists iddrawn($id)]
5185 drawcmitrow $r
5186 if {$r == $er} break
5187 set nextid [lindex $displayorder [expr {$r + 1}]]
5188 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5189 drawparentlinks $id $r
5191 set rowids [lindex $rowidlist $r]
5192 foreach lid $rowids {
5193 if {$lid eq {}} continue
5194 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5195 if {$lid eq $id} {
5196 # see if this is the first child of any of its parents
5197 foreach p [lindex $parentlist $r] {
5198 if {[lsearch -exact $rowids $p] < 0} {
5199 # make this line extend up to the child
5200 set lineend($p) [drawlineseg $p $r $er 0]
5203 } else {
5204 set lineend($lid) [drawlineseg $lid $r $er 1]
5210 proc undolayout {row} {
5211 global uparrowlen mingaplen downarrowlen
5212 global rowidlist rowisopt rowfinal need_redisplay
5214 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5215 if {$r < 0} {
5216 set r 0
5218 if {[llength $rowidlist] > $r} {
5219 incr r -1
5220 set rowidlist [lrange $rowidlist 0 $r]
5221 set rowfinal [lrange $rowfinal 0 $r]
5222 set rowisopt [lrange $rowisopt 0 $r]
5223 set need_redisplay 1
5224 run drawvisible
5228 proc drawvisible {} {
5229 global canv linespc curview vrowmod selectedline targetrow targetid
5230 global need_redisplay cscroll numcommits
5232 set fs [$canv yview]
5233 set ymax [lindex [$canv cget -scrollregion] 3]
5234 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5235 set f0 [lindex $fs 0]
5236 set f1 [lindex $fs 1]
5237 set y0 [expr {int($f0 * $ymax)}]
5238 set y1 [expr {int($f1 * $ymax)}]
5240 if {[info exists targetid]} {
5241 if {[commitinview $targetid $curview]} {
5242 set r [rowofcommit $targetid]
5243 if {$r != $targetrow} {
5244 # Fix up the scrollregion and change the scrolling position
5245 # now that our target row has moved.
5246 set diff [expr {($r - $targetrow) * $linespc}]
5247 set targetrow $r
5248 setcanvscroll
5249 set ymax [lindex [$canv cget -scrollregion] 3]
5250 incr y0 $diff
5251 incr y1 $diff
5252 set f0 [expr {$y0 / $ymax}]
5253 set f1 [expr {$y1 / $ymax}]
5254 allcanvs yview moveto $f0
5255 $cscroll set $f0 $f1
5256 set need_redisplay 1
5258 } else {
5259 unset targetid
5263 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5264 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5265 if {$endrow >= $vrowmod($curview)} {
5266 update_arcrows $curview
5268 if {$selectedline ne {} &&
5269 $row <= $selectedline && $selectedline <= $endrow} {
5270 set targetrow $selectedline
5271 } elseif {[info exists targetid]} {
5272 set targetrow [expr {int(($row + $endrow) / 2)}]
5274 if {[info exists targetrow]} {
5275 if {$targetrow >= $numcommits} {
5276 set targetrow [expr {$numcommits - 1}]
5278 set targetid [commitonrow $targetrow]
5280 drawcommits $row $endrow
5283 proc clear_display {} {
5284 global iddrawn linesegs need_redisplay nrows_drawn
5285 global vhighlights fhighlights nhighlights rhighlights
5286 global linehtag linentag linedtag boldrows boldnamerows
5288 allcanvs delete all
5289 catch {unset iddrawn}
5290 catch {unset linesegs}
5291 catch {unset linehtag}
5292 catch {unset linentag}
5293 catch {unset linedtag}
5294 set boldrows {}
5295 set boldnamerows {}
5296 catch {unset vhighlights}
5297 catch {unset fhighlights}
5298 catch {unset nhighlights}
5299 catch {unset rhighlights}
5300 set need_redisplay 0
5301 set nrows_drawn 0
5304 proc findcrossings {id} {
5305 global rowidlist parentlist numcommits displayorder
5307 set cross {}
5308 set ccross {}
5309 foreach {s e} [rowranges $id] {
5310 if {$e >= $numcommits} {
5311 set e [expr {$numcommits - 1}]
5313 if {$e <= $s} continue
5314 for {set row $e} {[incr row -1] >= $s} {} {
5315 set x [lsearch -exact [lindex $rowidlist $row] $id]
5316 if {$x < 0} break
5317 set olds [lindex $parentlist $row]
5318 set kid [lindex $displayorder $row]
5319 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5320 if {$kidx < 0} continue
5321 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5322 foreach p $olds {
5323 set px [lsearch -exact $nextrow $p]
5324 if {$px < 0} continue
5325 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5326 if {[lsearch -exact $ccross $p] >= 0} continue
5327 if {$x == $px + ($kidx < $px? -1: 1)} {
5328 lappend ccross $p
5329 } elseif {[lsearch -exact $cross $p] < 0} {
5330 lappend cross $p
5336 return [concat $ccross {{}} $cross]
5339 proc assigncolor {id} {
5340 global colormap colors nextcolor
5341 global parents children children curview
5343 if {[info exists colormap($id)]} return
5344 set ncolors [llength $colors]
5345 if {[info exists children($curview,$id)]} {
5346 set kids $children($curview,$id)
5347 } else {
5348 set kids {}
5350 if {[llength $kids] == 1} {
5351 set child [lindex $kids 0]
5352 if {[info exists colormap($child)]
5353 && [llength $parents($curview,$child)] == 1} {
5354 set colormap($id) $colormap($child)
5355 return
5358 set badcolors {}
5359 set origbad {}
5360 foreach x [findcrossings $id] {
5361 if {$x eq {}} {
5362 # delimiter between corner crossings and other crossings
5363 if {[llength $badcolors] >= $ncolors - 1} break
5364 set origbad $badcolors
5366 if {[info exists colormap($x)]
5367 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5368 lappend badcolors $colormap($x)
5371 if {[llength $badcolors] >= $ncolors} {
5372 set badcolors $origbad
5374 set origbad $badcolors
5375 if {[llength $badcolors] < $ncolors - 1} {
5376 foreach child $kids {
5377 if {[info exists colormap($child)]
5378 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5379 lappend badcolors $colormap($child)
5381 foreach p $parents($curview,$child) {
5382 if {[info exists colormap($p)]
5383 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5384 lappend badcolors $colormap($p)
5388 if {[llength $badcolors] >= $ncolors} {
5389 set badcolors $origbad
5392 for {set i 0} {$i <= $ncolors} {incr i} {
5393 set c [lindex $colors $nextcolor]
5394 if {[incr nextcolor] >= $ncolors} {
5395 set nextcolor 0
5397 if {[lsearch -exact $badcolors $c]} break
5399 set colormap($id) $c
5402 proc bindline {t id} {
5403 global canv
5405 $canv bind $t <Enter> "lineenter %x %y $id"
5406 $canv bind $t <Motion> "linemotion %x %y $id"
5407 $canv bind $t <Leave> "lineleave $id"
5408 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5411 proc drawtags {id x xt y1} {
5412 global idtags idheads idotherrefs mainhead
5413 global linespc lthickness
5414 global canv rowtextx curview fgcolor bgcolor ctxbut
5416 set marks {}
5417 set ntags 0
5418 set nheads 0
5419 if {[info exists idtags($id)]} {
5420 set marks $idtags($id)
5421 set ntags [llength $marks]
5423 if {[info exists idheads($id)]} {
5424 set marks [concat $marks $idheads($id)]
5425 set nheads [llength $idheads($id)]
5427 if {[info exists idotherrefs($id)]} {
5428 set marks [concat $marks $idotherrefs($id)]
5430 if {$marks eq {}} {
5431 return $xt
5434 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5435 set yt [expr {$y1 - 0.5 * $linespc}]
5436 set yb [expr {$yt + $linespc - 1}]
5437 set xvals {}
5438 set wvals {}
5439 set i -1
5440 foreach tag $marks {
5441 incr i
5442 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5443 set wid [font measure mainfontbold $tag]
5444 } else {
5445 set wid [font measure mainfont $tag]
5447 lappend xvals $xt
5448 lappend wvals $wid
5449 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5451 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5452 -width $lthickness -fill black -tags tag.$id]
5453 $canv lower $t
5454 foreach tag $marks x $xvals wid $wvals {
5455 set xl [expr {$x + $delta}]
5456 set xr [expr {$x + $delta + $wid + $lthickness}]
5457 set font mainfont
5458 if {[incr ntags -1] >= 0} {
5459 # draw a tag
5460 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5461 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5462 -width 1 -outline black -fill yellow -tags tag.$id]
5463 $canv bind $t <1> [list showtag $tag 1]
5464 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5465 } else {
5466 # draw a head or other ref
5467 if {[incr nheads -1] >= 0} {
5468 set col green
5469 if {$tag eq $mainhead} {
5470 set font mainfontbold
5472 } else {
5473 set col "#ddddff"
5475 set xl [expr {$xl - $delta/2}]
5476 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5477 -width 1 -outline black -fill $col -tags tag.$id
5478 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5479 set rwid [font measure mainfont $remoteprefix]
5480 set xi [expr {$x + 1}]
5481 set yti [expr {$yt + 1}]
5482 set xri [expr {$x + $rwid}]
5483 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5484 -width 0 -fill "#ffddaa" -tags tag.$id
5487 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5488 -font $font -tags [list tag.$id text]]
5489 if {$ntags >= 0} {
5490 $canv bind $t <1> [list showtag $tag 1]
5491 } elseif {$nheads >= 0} {
5492 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
5495 return $xt
5498 proc xcoord {i level ln} {
5499 global canvx0 xspc1 xspc2
5501 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5502 if {$i > 0 && $i == $level} {
5503 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5504 } elseif {$i > $level} {
5505 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5507 return $x
5510 proc show_status {msg} {
5511 global canv fgcolor
5513 clear_display
5514 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5515 -tags text -fill $fgcolor
5518 # Don't change the text pane cursor if it is currently the hand cursor,
5519 # showing that we are over a sha1 ID link.
5520 proc settextcursor {c} {
5521 global ctext curtextcursor
5523 if {[$ctext cget -cursor] == $curtextcursor} {
5524 $ctext config -cursor $c
5526 set curtextcursor $c
5529 proc nowbusy {what {name {}}} {
5530 global isbusy busyname statusw
5532 if {[array names isbusy] eq {}} {
5533 . config -cursor watch
5534 settextcursor watch
5536 set isbusy($what) 1
5537 set busyname($what) $name
5538 if {$name ne {}} {
5539 $statusw conf -text $name
5543 proc notbusy {what} {
5544 global isbusy maincursor textcursor busyname statusw
5546 catch {
5547 unset isbusy($what)
5548 if {$busyname($what) ne {} &&
5549 [$statusw cget -text] eq $busyname($what)} {
5550 $statusw conf -text {}
5553 if {[array names isbusy] eq {}} {
5554 . config -cursor $maincursor
5555 settextcursor $textcursor
5559 proc findmatches {f} {
5560 global findtype findstring
5561 if {$findtype == [mc "Regexp"]} {
5562 set matches [regexp -indices -all -inline $findstring $f]
5563 } else {
5564 set fs $findstring
5565 if {$findtype == [mc "IgnCase"]} {
5566 set f [string tolower $f]
5567 set fs [string tolower $fs]
5569 set matches {}
5570 set i 0
5571 set l [string length $fs]
5572 while {[set j [string first $fs $f $i]] >= 0} {
5573 lappend matches [list $j [expr {$j+$l-1}]]
5574 set i [expr {$j + $l}]
5577 return $matches
5580 proc dofind {{dirn 1} {wrap 1}} {
5581 global findstring findstartline findcurline selectedline numcommits
5582 global gdttype filehighlight fh_serial find_dirn findallowwrap
5584 if {[info exists find_dirn]} {
5585 if {$find_dirn == $dirn} return
5586 stopfinding
5588 focus .
5589 if {$findstring eq {} || $numcommits == 0} return
5590 if {$selectedline eq {}} {
5591 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
5592 } else {
5593 set findstartline $selectedline
5595 set findcurline $findstartline
5596 nowbusy finding [mc "Searching"]
5597 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
5598 after cancel do_file_hl $fh_serial
5599 do_file_hl $fh_serial
5601 set find_dirn $dirn
5602 set findallowwrap $wrap
5603 run findmore
5606 proc stopfinding {} {
5607 global find_dirn findcurline fprogcoord
5609 if {[info exists find_dirn]} {
5610 unset find_dirn
5611 unset findcurline
5612 notbusy finding
5613 set fprogcoord 0
5614 adjustprogress
5618 proc findmore {} {
5619 global commitdata commitinfo numcommits findpattern findloc
5620 global findstartline findcurline findallowwrap
5621 global find_dirn gdttype fhighlights fprogcoord
5622 global curview varcorder vrownum varccommits vrowmod
5624 if {![info exists find_dirn]} {
5625 return 0
5627 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
5628 set l $findcurline
5629 set moretodo 0
5630 if {$find_dirn > 0} {
5631 incr l
5632 if {$l >= $numcommits} {
5633 set l 0
5635 if {$l <= $findstartline} {
5636 set lim [expr {$findstartline + 1}]
5637 } else {
5638 set lim $numcommits
5639 set moretodo $findallowwrap
5641 } else {
5642 if {$l == 0} {
5643 set l $numcommits
5645 incr l -1
5646 if {$l >= $findstartline} {
5647 set lim [expr {$findstartline - 1}]
5648 } else {
5649 set lim -1
5650 set moretodo $findallowwrap
5653 set n [expr {($lim - $l) * $find_dirn}]
5654 if {$n > 500} {
5655 set n 500
5656 set moretodo 1
5658 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
5659 update_arcrows $curview
5661 set found 0
5662 set domore 1
5663 set ai [bsearch $vrownum($curview) $l]
5664 set a [lindex $varcorder($curview) $ai]
5665 set arow [lindex $vrownum($curview) $ai]
5666 set ids [lindex $varccommits($curview,$a)]
5667 set arowend [expr {$arow + [llength $ids]}]
5668 if {$gdttype eq [mc "containing:"]} {
5669 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5670 if {$l < $arow || $l >= $arowend} {
5671 incr ai $find_dirn
5672 set a [lindex $varcorder($curview) $ai]
5673 set arow [lindex $vrownum($curview) $ai]
5674 set ids [lindex $varccommits($curview,$a)]
5675 set arowend [expr {$arow + [llength $ids]}]
5677 set id [lindex $ids [expr {$l - $arow}]]
5678 # shouldn't happen unless git log doesn't give all the commits...
5679 if {![info exists commitdata($id)] ||
5680 ![doesmatch $commitdata($id)]} {
5681 continue
5683 if {![info exists commitinfo($id)]} {
5684 getcommit $id
5686 set info $commitinfo($id)
5687 foreach f $info ty $fldtypes {
5688 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
5689 [doesmatch $f]} {
5690 set found 1
5691 break
5694 if {$found} break
5696 } else {
5697 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5698 if {$l < $arow || $l >= $arowend} {
5699 incr ai $find_dirn
5700 set a [lindex $varcorder($curview) $ai]
5701 set arow [lindex $vrownum($curview) $ai]
5702 set ids [lindex $varccommits($curview,$a)]
5703 set arowend [expr {$arow + [llength $ids]}]
5705 set id [lindex $ids [expr {$l - $arow}]]
5706 if {![info exists fhighlights($id)]} {
5707 # this sets fhighlights($id) to -1
5708 askfilehighlight $l $id
5710 if {$fhighlights($id) > 0} {
5711 set found $domore
5712 break
5714 if {$fhighlights($id) < 0} {
5715 if {$domore} {
5716 set domore 0
5717 set findcurline [expr {$l - $find_dirn}]
5722 if {$found || ($domore && !$moretodo)} {
5723 unset findcurline
5724 unset find_dirn
5725 notbusy finding
5726 set fprogcoord 0
5727 adjustprogress
5728 if {$found} {
5729 findselectline $l
5730 } else {
5731 bell
5733 return 0
5735 if {!$domore} {
5736 flushhighlights
5737 } else {
5738 set findcurline [expr {$l - $find_dirn}]
5740 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
5741 if {$n < 0} {
5742 incr n $numcommits
5744 set fprogcoord [expr {$n * 1.0 / $numcommits}]
5745 adjustprogress
5746 return $domore
5749 proc findselectline {l} {
5750 global findloc commentend ctext findcurline markingmatches gdttype
5752 set markingmatches 1
5753 set findcurline $l
5754 selectline $l 1
5755 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
5756 # highlight the matches in the comments
5757 set f [$ctext get 1.0 $commentend]
5758 set matches [findmatches $f]
5759 foreach match $matches {
5760 set start [lindex $match 0]
5761 set end [expr {[lindex $match 1] + 1}]
5762 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5765 drawvisible
5768 # mark the bits of a headline or author that match a find string
5769 proc markmatches {canv l str tag matches font row} {
5770 global selectedline
5772 set bbox [$canv bbox $tag]
5773 set x0 [lindex $bbox 0]
5774 set y0 [lindex $bbox 1]
5775 set y1 [lindex $bbox 3]
5776 foreach match $matches {
5777 set start [lindex $match 0]
5778 set end [lindex $match 1]
5779 if {$start > $end} continue
5780 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
5781 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
5782 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
5783 [expr {$x0+$xlen+2}] $y1 \
5784 -outline {} -tags [list match$l matches] -fill yellow]
5785 $canv lower $t
5786 if {$row == $selectedline} {
5787 $canv raise $t secsel
5792 proc unmarkmatches {} {
5793 global markingmatches
5795 allcanvs delete matches
5796 set markingmatches 0
5797 stopfinding
5800 proc selcanvline {w x y} {
5801 global canv canvy0 ctext linespc
5802 global rowtextx
5803 set ymax [lindex [$canv cget -scrollregion] 3]
5804 if {$ymax == {}} return
5805 set yfrac [lindex [$canv yview] 0]
5806 set y [expr {$y + $yfrac * $ymax}]
5807 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
5808 if {$l < 0} {
5809 set l 0
5811 if {$w eq $canv} {
5812 set xmax [lindex [$canv cget -scrollregion] 2]
5813 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
5814 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
5816 unmarkmatches
5817 selectline $l 1
5820 proc commit_descriptor {p} {
5821 global commitinfo
5822 if {![info exists commitinfo($p)]} {
5823 getcommit $p
5825 set l "..."
5826 if {[llength $commitinfo($p)] > 1} {
5827 set l [lindex $commitinfo($p) 0]
5829 return "$p ($l)\n"
5832 # append some text to the ctext widget, and make any SHA1 ID
5833 # that we know about be a clickable link.
5834 proc appendwithlinks {text tags} {
5835 global ctext linknum curview
5837 set start [$ctext index "end - 1c"]
5838 $ctext insert end $text $tags
5839 set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
5840 foreach l $links {
5841 set s [lindex $l 0]
5842 set e [lindex $l 1]
5843 set linkid [string range $text $s $e]
5844 incr e
5845 $ctext tag delete link$linknum
5846 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
5847 setlink $linkid link$linknum
5848 incr linknum
5852 proc setlink {id lk} {
5853 global curview ctext pendinglinks
5855 set known 0
5856 if {[string length $id] < 40} {
5857 set matches [longid $id]
5858 if {[llength $matches] > 0} {
5859 if {[llength $matches] > 1} return
5860 set known 1
5861 set id [lindex $matches 0]
5863 } else {
5864 set known [commitinview $id $curview]
5866 if {$known} {
5867 $ctext tag conf $lk -foreground blue -underline 1
5868 $ctext tag bind $lk <1> [list selbyid $id]
5869 $ctext tag bind $lk <Enter> {linkcursor %W 1}
5870 $ctext tag bind $lk <Leave> {linkcursor %W -1}
5871 } else {
5872 lappend pendinglinks($id) $lk
5873 interestedin $id {makelink %P}
5877 proc makelink {id} {
5878 global pendinglinks
5880 if {![info exists pendinglinks($id)]} return
5881 foreach lk $pendinglinks($id) {
5882 setlink $id $lk
5884 unset pendinglinks($id)
5887 proc linkcursor {w inc} {
5888 global linkentercount curtextcursor
5890 if {[incr linkentercount $inc] > 0} {
5891 $w configure -cursor hand2
5892 } else {
5893 $w configure -cursor $curtextcursor
5894 if {$linkentercount < 0} {
5895 set linkentercount 0
5900 proc viewnextline {dir} {
5901 global canv linespc
5903 $canv delete hover
5904 set ymax [lindex [$canv cget -scrollregion] 3]
5905 set wnow [$canv yview]
5906 set wtop [expr {[lindex $wnow 0] * $ymax}]
5907 set newtop [expr {$wtop + $dir * $linespc}]
5908 if {$newtop < 0} {
5909 set newtop 0
5910 } elseif {$newtop > $ymax} {
5911 set newtop $ymax
5913 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5916 # add a list of tag or branch names at position pos
5917 # returns the number of names inserted
5918 proc appendrefs {pos ids var} {
5919 global ctext linknum curview $var maxrefs
5921 if {[catch {$ctext index $pos}]} {
5922 return 0
5924 $ctext conf -state normal
5925 $ctext delete $pos "$pos lineend"
5926 set tags {}
5927 foreach id $ids {
5928 foreach tag [set $var\($id\)] {
5929 lappend tags [list $tag $id]
5932 if {[llength $tags] > $maxrefs} {
5933 $ctext insert $pos "many ([llength $tags])"
5934 } else {
5935 set tags [lsort -index 0 -decreasing $tags]
5936 set sep {}
5937 foreach ti $tags {
5938 set id [lindex $ti 1]
5939 set lk link$linknum
5940 incr linknum
5941 $ctext tag delete $lk
5942 $ctext insert $pos $sep
5943 $ctext insert $pos [lindex $ti 0] $lk
5944 setlink $id $lk
5945 set sep ", "
5948 $ctext conf -state disabled
5949 return [llength $tags]
5952 # called when we have finished computing the nearby tags
5953 proc dispneartags {delay} {
5954 global selectedline currentid showneartags tagphase
5956 if {$selectedline eq {} || !$showneartags} return
5957 after cancel dispnexttag
5958 if {$delay} {
5959 after 200 dispnexttag
5960 set tagphase -1
5961 } else {
5962 after idle dispnexttag
5963 set tagphase 0
5967 proc dispnexttag {} {
5968 global selectedline currentid showneartags tagphase ctext
5970 if {$selectedline eq {} || !$showneartags} return
5971 switch -- $tagphase {
5973 set dtags [desctags $currentid]
5974 if {$dtags ne {}} {
5975 appendrefs precedes $dtags idtags
5979 set atags [anctags $currentid]
5980 if {$atags ne {}} {
5981 appendrefs follows $atags idtags
5985 set dheads [descheads $currentid]
5986 if {$dheads ne {}} {
5987 if {[appendrefs branch $dheads idheads] > 1
5988 && [$ctext get "branch -3c"] eq "h"} {
5989 # turn "Branch" into "Branches"
5990 $ctext conf -state normal
5991 $ctext insert "branch -2c" "es"
5992 $ctext conf -state disabled
5997 if {[incr tagphase] <= 2} {
5998 after idle dispnexttag
6002 proc make_secsel {l} {
6003 global linehtag linentag linedtag canv canv2 canv3
6005 if {![info exists linehtag($l)]} return
6006 $canv delete secsel
6007 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
6008 -tags secsel -fill [$canv cget -selectbackground]]
6009 $canv lower $t
6010 $canv2 delete secsel
6011 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
6012 -tags secsel -fill [$canv2 cget -selectbackground]]
6013 $canv2 lower $t
6014 $canv3 delete secsel
6015 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
6016 -tags secsel -fill [$canv3 cget -selectbackground]]
6017 $canv3 lower $t
6020 proc selectline {l isnew} {
6021 global canv ctext commitinfo selectedline
6022 global canvy0 linespc parents children curview
6023 global currentid sha1entry
6024 global commentend idtags linknum
6025 global mergemax numcommits pending_select
6026 global cmitmode showneartags allcommits
6027 global targetrow targetid lastscrollrows
6028 global autoselect
6030 catch {unset pending_select}
6031 $canv delete hover
6032 normalline
6033 unsel_reflist
6034 stopfinding
6035 if {$l < 0 || $l >= $numcommits} return
6036 set id [commitonrow $l]
6037 set targetid $id
6038 set targetrow $l
6039 set selectedline $l
6040 set currentid $id
6041 if {$lastscrollrows < $numcommits} {
6042 setcanvscroll
6045 set y [expr {$canvy0 + $l * $linespc}]
6046 set ymax [lindex [$canv cget -scrollregion] 3]
6047 set ytop [expr {$y - $linespc - 1}]
6048 set ybot [expr {$y + $linespc + 1}]
6049 set wnow [$canv yview]
6050 set wtop [expr {[lindex $wnow 0] * $ymax}]
6051 set wbot [expr {[lindex $wnow 1] * $ymax}]
6052 set wh [expr {$wbot - $wtop}]
6053 set newtop $wtop
6054 if {$ytop < $wtop} {
6055 if {$ybot < $wtop} {
6056 set newtop [expr {$y - $wh / 2.0}]
6057 } else {
6058 set newtop $ytop
6059 if {$newtop > $wtop - $linespc} {
6060 set newtop [expr {$wtop - $linespc}]
6063 } elseif {$ybot > $wbot} {
6064 if {$ytop > $wbot} {
6065 set newtop [expr {$y - $wh / 2.0}]
6066 } else {
6067 set newtop [expr {$ybot - $wh}]
6068 if {$newtop < $wtop + $linespc} {
6069 set newtop [expr {$wtop + $linespc}]
6073 if {$newtop != $wtop} {
6074 if {$newtop < 0} {
6075 set newtop 0
6077 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6078 drawvisible
6081 make_secsel $l
6083 if {$isnew} {
6084 addtohistory [list selbyid $id]
6087 $sha1entry delete 0 end
6088 $sha1entry insert 0 $id
6089 if {$autoselect} {
6090 $sha1entry selection from 0
6091 $sha1entry selection to end
6093 rhighlight_sel $id
6095 $ctext conf -state normal
6096 clear_ctext
6097 set linknum 0
6098 if {![info exists commitinfo($id)]} {
6099 getcommit $id
6101 set info $commitinfo($id)
6102 set date [formatdate [lindex $info 2]]
6103 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
6104 set date [formatdate [lindex $info 4]]
6105 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
6106 if {[info exists idtags($id)]} {
6107 $ctext insert end [mc "Tags:"]
6108 foreach tag $idtags($id) {
6109 $ctext insert end " $tag"
6111 $ctext insert end "\n"
6114 set headers {}
6115 set olds $parents($curview,$id)
6116 if {[llength $olds] > 1} {
6117 set np 0
6118 foreach p $olds {
6119 if {$np >= $mergemax} {
6120 set tag mmax
6121 } else {
6122 set tag m$np
6124 $ctext insert end "[mc "Parent"]: " $tag
6125 appendwithlinks [commit_descriptor $p] {}
6126 incr np
6128 } else {
6129 foreach p $olds {
6130 append headers "[mc "Parent"]: [commit_descriptor $p]"
6134 foreach c $children($curview,$id) {
6135 append headers "[mc "Child"]: [commit_descriptor $c]"
6138 # make anything that looks like a SHA1 ID be a clickable link
6139 appendwithlinks $headers {}
6140 if {$showneartags} {
6141 if {![info exists allcommits]} {
6142 getallcommits
6144 $ctext insert end "[mc "Branch"]: "
6145 $ctext mark set branch "end -1c"
6146 $ctext mark gravity branch left
6147 $ctext insert end "\n[mc "Follows"]: "
6148 $ctext mark set follows "end -1c"
6149 $ctext mark gravity follows left
6150 $ctext insert end "\n[mc "Precedes"]: "
6151 $ctext mark set precedes "end -1c"
6152 $ctext mark gravity precedes left
6153 $ctext insert end "\n"
6154 dispneartags 1
6156 $ctext insert end "\n"
6157 set comment [lindex $info 5]
6158 if {[string first "\r" $comment] >= 0} {
6159 set comment [string map {"\r" "\n "} $comment]
6161 appendwithlinks $comment {comment}
6163 $ctext tag remove found 1.0 end
6164 $ctext conf -state disabled
6165 set commentend [$ctext index "end - 1c"]
6167 init_flist [mc "Comments"]
6168 if {$cmitmode eq "tree"} {
6169 gettree $id
6170 } elseif {[llength $olds] <= 1} {
6171 startdiff $id
6172 } else {
6173 mergediff $id
6177 proc selfirstline {} {
6178 unmarkmatches
6179 selectline 0 1
6182 proc sellastline {} {
6183 global numcommits
6184 unmarkmatches
6185 set l [expr {$numcommits - 1}]
6186 selectline $l 1
6189 proc selnextline {dir} {
6190 global selectedline
6191 focus .
6192 if {$selectedline eq {}} return
6193 set l [expr {$selectedline + $dir}]
6194 unmarkmatches
6195 selectline $l 1
6198 proc selnextpage {dir} {
6199 global canv linespc selectedline numcommits
6201 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6202 if {$lpp < 1} {
6203 set lpp 1
6205 allcanvs yview scroll [expr {$dir * $lpp}] units
6206 drawvisible
6207 if {$selectedline eq {}} return
6208 set l [expr {$selectedline + $dir * $lpp}]
6209 if {$l < 0} {
6210 set l 0
6211 } elseif {$l >= $numcommits} {
6212 set l [expr $numcommits - 1]
6214 unmarkmatches
6215 selectline $l 1
6218 proc unselectline {} {
6219 global selectedline currentid
6221 set selectedline {}
6222 catch {unset currentid}
6223 allcanvs delete secsel
6224 rhighlight_none
6227 proc reselectline {} {
6228 global selectedline
6230 if {$selectedline ne {}} {
6231 selectline $selectedline 0
6235 proc addtohistory {cmd} {
6236 global history historyindex curview
6238 set elt [list $curview $cmd]
6239 if {$historyindex > 0
6240 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6241 return
6244 if {$historyindex < [llength $history]} {
6245 set history [lreplace $history $historyindex end $elt]
6246 } else {
6247 lappend history $elt
6249 incr historyindex
6250 if {$historyindex > 1} {
6251 .tf.bar.leftbut conf -state normal
6252 } else {
6253 .tf.bar.leftbut conf -state disabled
6255 .tf.bar.rightbut conf -state disabled
6258 proc godo {elt} {
6259 global curview
6261 set view [lindex $elt 0]
6262 set cmd [lindex $elt 1]
6263 if {$curview != $view} {
6264 showview $view
6266 eval $cmd
6269 proc goback {} {
6270 global history historyindex
6271 focus .
6273 if {$historyindex > 1} {
6274 incr historyindex -1
6275 godo [lindex $history [expr {$historyindex - 1}]]
6276 .tf.bar.rightbut conf -state normal
6278 if {$historyindex <= 1} {
6279 .tf.bar.leftbut conf -state disabled
6283 proc goforw {} {
6284 global history historyindex
6285 focus .
6287 if {$historyindex < [llength $history]} {
6288 set cmd [lindex $history $historyindex]
6289 incr historyindex
6290 godo $cmd
6291 .tf.bar.leftbut conf -state normal
6293 if {$historyindex >= [llength $history]} {
6294 .tf.bar.rightbut conf -state disabled
6298 proc gettree {id} {
6299 global treefilelist treeidlist diffids diffmergeid treepending
6300 global nullid nullid2
6302 set diffids $id
6303 catch {unset diffmergeid}
6304 if {![info exists treefilelist($id)]} {
6305 if {![info exists treepending]} {
6306 if {$id eq $nullid} {
6307 set cmd [list | git ls-files]
6308 } elseif {$id eq $nullid2} {
6309 set cmd [list | git ls-files --stage -t]
6310 } else {
6311 set cmd [list | git ls-tree -r $id]
6313 if {[catch {set gtf [open $cmd r]}]} {
6314 return
6316 set treepending $id
6317 set treefilelist($id) {}
6318 set treeidlist($id) {}
6319 fconfigure $gtf -blocking 0 -encoding binary
6320 filerun $gtf [list gettreeline $gtf $id]
6322 } else {
6323 setfilelist $id
6327 proc gettreeline {gtf id} {
6328 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6330 set nl 0
6331 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6332 if {$diffids eq $nullid} {
6333 set fname $line
6334 } else {
6335 set i [string first "\t" $line]
6336 if {$i < 0} continue
6337 set fname [string range $line [expr {$i+1}] end]
6338 set line [string range $line 0 [expr {$i-1}]]
6339 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6340 set sha1 [lindex $line 2]
6341 lappend treeidlist($id) $sha1
6343 if {[string index $fname 0] eq "\""} {
6344 set fname [lindex $fname 0]
6346 set fname [encoding convertfrom $fname]
6347 lappend treefilelist($id) $fname
6349 if {![eof $gtf]} {
6350 return [expr {$nl >= 1000? 2: 1}]
6352 close $gtf
6353 unset treepending
6354 if {$cmitmode ne "tree"} {
6355 if {![info exists diffmergeid]} {
6356 gettreediffs $diffids
6358 } elseif {$id ne $diffids} {
6359 gettree $diffids
6360 } else {
6361 setfilelist $id
6363 return 0
6366 proc showfile {f} {
6367 global treefilelist treeidlist diffids nullid nullid2
6368 global ctext commentend
6370 set i [lsearch -exact $treefilelist($diffids) $f]
6371 if {$i < 0} {
6372 puts "oops, $f not in list for id $diffids"
6373 return
6375 if {$diffids eq $nullid} {
6376 if {[catch {set bf [open $f r]} err]} {
6377 puts "oops, can't read $f: $err"
6378 return
6380 } else {
6381 set blob [lindex $treeidlist($diffids) $i]
6382 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6383 puts "oops, error reading blob $blob: $err"
6384 return
6387 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
6388 filerun $bf [list getblobline $bf $diffids]
6389 $ctext config -state normal
6390 clear_ctext $commentend
6391 $ctext insert end "\n"
6392 $ctext insert end "$f\n" filesep
6393 $ctext config -state disabled
6394 $ctext yview $commentend
6395 settabs 0
6398 proc getblobline {bf id} {
6399 global diffids cmitmode ctext
6401 if {$id ne $diffids || $cmitmode ne "tree"} {
6402 catch {close $bf}
6403 return 0
6405 $ctext config -state normal
6406 set nl 0
6407 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6408 $ctext insert end "$line\n"
6410 if {[eof $bf]} {
6411 # delete last newline
6412 $ctext delete "end - 2c" "end - 1c"
6413 close $bf
6414 return 0
6416 $ctext config -state disabled
6417 return [expr {$nl >= 1000? 2: 1}]
6420 proc mergediff {id} {
6421 global diffmergeid mdifffd
6422 global diffids
6423 global parents
6424 global diffcontext
6425 global diffencoding
6426 global limitdiffs vfilelimit curview
6428 set diffmergeid $id
6429 set diffids $id
6430 # this doesn't seem to actually affect anything...
6431 set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
6432 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6433 set cmd [concat $cmd -- $vfilelimit($curview)]
6435 if {[catch {set mdf [open $cmd r]} err]} {
6436 error_popup "[mc "Error getting merge diffs:"] $err"
6437 return
6439 fconfigure $mdf -blocking 0 -encoding binary
6440 set mdifffd($id) $mdf
6441 set np [llength $parents($curview,$id)]
6442 set diffencoding [get_path_encoding {}]
6443 settabs $np
6444 filerun $mdf [list getmergediffline $mdf $id $np]
6447 proc getmergediffline {mdf id np} {
6448 global diffmergeid ctext cflist mergemax
6449 global difffilestart mdifffd
6450 global diffencoding
6452 $ctext conf -state normal
6453 set nr 0
6454 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
6455 if {![info exists diffmergeid] || $id != $diffmergeid
6456 || $mdf != $mdifffd($id)} {
6457 close $mdf
6458 return 0
6460 if {[regexp {^diff --cc (.*)} $line match fname]} {
6461 # start of a new file
6462 set fname [encoding convertfrom $fname]
6463 $ctext insert end "\n"
6464 set here [$ctext index "end - 1c"]
6465 lappend difffilestart $here
6466 add_flist [list $fname]
6467 set diffencoding [get_path_encoding $fname]
6468 set l [expr {(78 - [string length $fname]) / 2}]
6469 set pad [string range "----------------------------------------" 1 $l]
6470 $ctext insert end "$pad $fname $pad\n" filesep
6471 } elseif {[regexp {^@@} $line]} {
6472 set line [encoding convertfrom $diffencoding $line]
6473 $ctext insert end "$line\n" hunksep
6474 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
6475 # do nothing
6476 } else {
6477 set line [encoding convertfrom $diffencoding $line]
6478 # parse the prefix - one ' ', '-' or '+' for each parent
6479 set spaces {}
6480 set minuses {}
6481 set pluses {}
6482 set isbad 0
6483 for {set j 0} {$j < $np} {incr j} {
6484 set c [string range $line $j $j]
6485 if {$c == " "} {
6486 lappend spaces $j
6487 } elseif {$c == "-"} {
6488 lappend minuses $j
6489 } elseif {$c == "+"} {
6490 lappend pluses $j
6491 } else {
6492 set isbad 1
6493 break
6496 set tags {}
6497 set num {}
6498 if {!$isbad && $minuses ne {} && $pluses eq {}} {
6499 # line doesn't appear in result, parents in $minuses have the line
6500 set num [lindex $minuses 0]
6501 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
6502 # line appears in result, parents in $pluses don't have the line
6503 lappend tags mresult
6504 set num [lindex $spaces 0]
6506 if {$num ne {}} {
6507 if {$num >= $mergemax} {
6508 set num "max"
6510 lappend tags m$num
6512 $ctext insert end "$line\n" $tags
6515 $ctext conf -state disabled
6516 if {[eof $mdf]} {
6517 close $mdf
6518 return 0
6520 return [expr {$nr >= 1000? 2: 1}]
6523 proc startdiff {ids} {
6524 global treediffs diffids treepending diffmergeid nullid nullid2
6526 settabs 1
6527 set diffids $ids
6528 catch {unset diffmergeid}
6529 if {![info exists treediffs($ids)] ||
6530 [lsearch -exact $ids $nullid] >= 0 ||
6531 [lsearch -exact $ids $nullid2] >= 0} {
6532 if {![info exists treepending]} {
6533 gettreediffs $ids
6535 } else {
6536 addtocflist $ids
6540 proc path_filter {filter name} {
6541 foreach p $filter {
6542 set l [string length $p]
6543 if {[string index $p end] eq "/"} {
6544 if {[string compare -length $l $p $name] == 0} {
6545 return 1
6547 } else {
6548 if {[string compare -length $l $p $name] == 0 &&
6549 ([string length $name] == $l ||
6550 [string index $name $l] eq "/")} {
6551 return 1
6555 return 0
6558 proc addtocflist {ids} {
6559 global treediffs
6561 add_flist $treediffs($ids)
6562 getblobdiffs $ids
6565 proc diffcmd {ids flags} {
6566 global nullid nullid2
6568 set i [lsearch -exact $ids $nullid]
6569 set j [lsearch -exact $ids $nullid2]
6570 if {$i >= 0} {
6571 if {[llength $ids] > 1 && $j < 0} {
6572 # comparing working directory with some specific revision
6573 set cmd [concat | git diff-index $flags]
6574 if {$i == 0} {
6575 lappend cmd -R [lindex $ids 1]
6576 } else {
6577 lappend cmd [lindex $ids 0]
6579 } else {
6580 # comparing working directory with index
6581 set cmd [concat | git diff-files $flags]
6582 if {$j == 1} {
6583 lappend cmd -R
6586 } elseif {$j >= 0} {
6587 set cmd [concat | git diff-index --cached $flags]
6588 if {[llength $ids] > 1} {
6589 # comparing index with specific revision
6590 if {$i == 0} {
6591 lappend cmd -R [lindex $ids 1]
6592 } else {
6593 lappend cmd [lindex $ids 0]
6595 } else {
6596 # comparing index with HEAD
6597 lappend cmd HEAD
6599 } else {
6600 set cmd [concat | git diff-tree -r $flags $ids]
6602 return $cmd
6605 proc gettreediffs {ids} {
6606 global treediff treepending
6608 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6610 set treepending $ids
6611 set treediff {}
6612 fconfigure $gdtf -blocking 0 -encoding binary
6613 filerun $gdtf [list gettreediffline $gdtf $ids]
6616 proc gettreediffline {gdtf ids} {
6617 global treediff treediffs treepending diffids diffmergeid
6618 global cmitmode vfilelimit curview limitdiffs perfile_attrs
6620 set nr 0
6621 set sublist {}
6622 set max 1000
6623 if {$perfile_attrs} {
6624 # cache_gitattr is slow, and even slower on win32 where we
6625 # have to invoke it for only about 30 paths at a time
6626 set max 500
6627 if {[tk windowingsystem] == "win32"} {
6628 set max 120
6631 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
6632 set i [string first "\t" $line]
6633 if {$i >= 0} {
6634 set file [string range $line [expr {$i+1}] end]
6635 if {[string index $file 0] eq "\""} {
6636 set file [lindex $file 0]
6638 set file [encoding convertfrom $file]
6639 lappend treediff $file
6640 lappend sublist $file
6643 if {$perfile_attrs} {
6644 cache_gitattr encoding $sublist
6646 if {![eof $gdtf]} {
6647 return [expr {$nr >= $max? 2: 1}]
6649 close $gdtf
6650 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6651 set flist {}
6652 foreach f $treediff {
6653 if {[path_filter $vfilelimit($curview) $f]} {
6654 lappend flist $f
6657 set treediffs($ids) $flist
6658 } else {
6659 set treediffs($ids) $treediff
6661 unset treepending
6662 if {$cmitmode eq "tree"} {
6663 gettree $diffids
6664 } elseif {$ids != $diffids} {
6665 if {![info exists diffmergeid]} {
6666 gettreediffs $diffids
6668 } else {
6669 addtocflist $ids
6671 return 0
6674 # empty string or positive integer
6675 proc diffcontextvalidate {v} {
6676 return [regexp {^(|[1-9][0-9]*)$} $v]
6679 proc diffcontextchange {n1 n2 op} {
6680 global diffcontextstring diffcontext
6682 if {[string is integer -strict $diffcontextstring]} {
6683 if {$diffcontextstring > 0} {
6684 set diffcontext $diffcontextstring
6685 reselectline
6690 proc changeignorespace {} {
6691 reselectline
6694 proc getblobdiffs {ids} {
6695 global blobdifffd diffids env
6696 global diffinhdr treediffs
6697 global diffcontext
6698 global ignorespace
6699 global limitdiffs vfilelimit curview
6700 global diffencoding
6702 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
6703 if {$ignorespace} {
6704 append cmd " -w"
6706 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6707 set cmd [concat $cmd -- $vfilelimit($curview)]
6709 if {[catch {set bdf [open $cmd r]} err]} {
6710 puts "error getting diffs: $err"
6711 return
6713 set diffinhdr 0
6714 set diffencoding [get_path_encoding {}]
6715 fconfigure $bdf -blocking 0 -encoding binary
6716 set blobdifffd($ids) $bdf
6717 filerun $bdf [list getblobdiffline $bdf $diffids]
6720 proc setinlist {var i val} {
6721 global $var
6723 while {[llength [set $var]] < $i} {
6724 lappend $var {}
6726 if {[llength [set $var]] == $i} {
6727 lappend $var $val
6728 } else {
6729 lset $var $i $val
6733 proc makediffhdr {fname ids} {
6734 global ctext curdiffstart treediffs
6736 set i [lsearch -exact $treediffs($ids) $fname]
6737 if {$i >= 0} {
6738 setinlist difffilestart $i $curdiffstart
6740 set l [expr {(78 - [string length $fname]) / 2}]
6741 set pad [string range "----------------------------------------" 1 $l]
6742 $ctext insert $curdiffstart "$pad $fname $pad" filesep
6745 proc getblobdiffline {bdf ids} {
6746 global diffids blobdifffd ctext curdiffstart
6747 global diffnexthead diffnextnote difffilestart
6748 global diffinhdr treediffs
6749 global diffencoding
6751 set nr 0
6752 $ctext conf -state normal
6753 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
6754 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
6755 close $bdf
6756 return 0
6758 if {![string compare -length 11 "diff --git " $line]} {
6759 # trim off "diff --git "
6760 set line [string range $line 11 end]
6761 set diffinhdr 1
6762 # start of a new file
6763 $ctext insert end "\n"
6764 set curdiffstart [$ctext index "end - 1c"]
6765 $ctext insert end "\n" filesep
6766 # If the name hasn't changed the length will be odd,
6767 # the middle char will be a space, and the two bits either
6768 # side will be a/name and b/name, or "a/name" and "b/name".
6769 # If the name has changed we'll get "rename from" and
6770 # "rename to" or "copy from" and "copy to" lines following this,
6771 # and we'll use them to get the filenames.
6772 # This complexity is necessary because spaces in the filename(s)
6773 # don't get escaped.
6774 set l [string length $line]
6775 set i [expr {$l / 2}]
6776 if {!(($l & 1) && [string index $line $i] eq " " &&
6777 [string range $line 2 [expr {$i - 1}]] eq \
6778 [string range $line [expr {$i + 3}] end])} {
6779 continue
6781 # unescape if quoted and chop off the a/ from the front
6782 if {[string index $line 0] eq "\""} {
6783 set fname [string range [lindex $line 0] 2 end]
6784 } else {
6785 set fname [string range $line 2 [expr {$i - 1}]]
6787 set fname [encoding convertfrom $fname]
6788 set diffencoding [get_path_encoding $fname]
6789 makediffhdr $fname $ids
6791 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
6792 $line match f1l f1c f2l f2c rest]} {
6793 set line [encoding convertfrom $diffencoding $line]
6794 $ctext insert end "$line\n" hunksep
6795 set diffinhdr 0
6797 } elseif {$diffinhdr} {
6798 if {![string compare -length 12 "rename from " $line]} {
6799 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
6800 if {[string index $fname 0] eq "\""} {
6801 set fname [lindex $fname 0]
6803 set fname [encoding convertfrom $fname]
6804 set i [lsearch -exact $treediffs($ids) $fname]
6805 if {$i >= 0} {
6806 setinlist difffilestart $i $curdiffstart
6808 } elseif {![string compare -length 10 $line "rename to "] ||
6809 ![string compare -length 8 $line "copy to "]} {
6810 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
6811 if {[string index $fname 0] eq "\""} {
6812 set fname [lindex $fname 0]
6814 set fname [encoding convertfrom $fname]
6815 set diffencoding [get_path_encoding $fname]
6816 makediffhdr $fname $ids
6817 } elseif {[string compare -length 3 $line "---"] == 0} {
6818 # do nothing
6819 continue
6820 } elseif {[string compare -length 3 $line "+++"] == 0} {
6821 set diffinhdr 0
6822 continue
6824 $ctext insert end "$line\n" filesep
6826 } else {
6827 set line [encoding convertfrom $diffencoding $line]
6828 set x [string range $line 0 0]
6829 if {$x == "-" || $x == "+"} {
6830 set tag [expr {$x == "+"}]
6831 $ctext insert end "$line\n" d$tag
6832 } elseif {$x == " "} {
6833 $ctext insert end "$line\n"
6834 } else {
6835 # "\ No newline at end of file",
6836 # or something else we don't recognize
6837 $ctext insert end "$line\n" hunksep
6841 $ctext conf -state disabled
6842 if {[eof $bdf]} {
6843 close $bdf
6844 return 0
6846 return [expr {$nr >= 1000? 2: 1}]
6849 proc changediffdisp {} {
6850 global ctext diffelide
6852 $ctext tag conf d0 -elide [lindex $diffelide 0]
6853 $ctext tag conf d1 -elide [lindex $diffelide 1]
6856 proc highlightfile {loc cline} {
6857 global ctext cflist cflist_top
6859 $ctext yview $loc
6860 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
6861 $cflist tag add highlight $cline.0 "$cline.0 lineend"
6862 $cflist see $cline.0
6863 set cflist_top $cline
6866 proc prevfile {} {
6867 global difffilestart ctext cmitmode
6869 if {$cmitmode eq "tree"} return
6870 set prev 0.0
6871 set prevline 1
6872 set here [$ctext index @0,0]
6873 foreach loc $difffilestart {
6874 if {[$ctext compare $loc >= $here]} {
6875 highlightfile $prev $prevline
6876 return
6878 set prev $loc
6879 incr prevline
6881 highlightfile $prev $prevline
6884 proc nextfile {} {
6885 global difffilestart ctext cmitmode
6887 if {$cmitmode eq "tree"} return
6888 set here [$ctext index @0,0]
6889 set line 1
6890 foreach loc $difffilestart {
6891 incr line
6892 if {[$ctext compare $loc > $here]} {
6893 highlightfile $loc $line
6894 return
6899 proc clear_ctext {{first 1.0}} {
6900 global ctext smarktop smarkbot
6901 global pendinglinks
6903 set l [lindex [split $first .] 0]
6904 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
6905 set smarktop $l
6907 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
6908 set smarkbot $l
6910 $ctext delete $first end
6911 if {$first eq "1.0"} {
6912 catch {unset pendinglinks}
6916 proc settabs {{firstab {}}} {
6917 global firsttabstop tabstop ctext have_tk85
6919 if {$firstab ne {} && $have_tk85} {
6920 set firsttabstop $firstab
6922 set w [font measure textfont "0"]
6923 if {$firsttabstop != 0} {
6924 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
6925 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
6926 } elseif {$have_tk85 || $tabstop != 8} {
6927 $ctext conf -tabs [expr {$tabstop * $w}]
6928 } else {
6929 $ctext conf -tabs {}
6933 proc incrsearch {name ix op} {
6934 global ctext searchstring searchdirn
6936 $ctext tag remove found 1.0 end
6937 if {[catch {$ctext index anchor}]} {
6938 # no anchor set, use start of selection, or of visible area
6939 set sel [$ctext tag ranges sel]
6940 if {$sel ne {}} {
6941 $ctext mark set anchor [lindex $sel 0]
6942 } elseif {$searchdirn eq "-forwards"} {
6943 $ctext mark set anchor @0,0
6944 } else {
6945 $ctext mark set anchor @0,[winfo height $ctext]
6948 if {$searchstring ne {}} {
6949 set here [$ctext search $searchdirn -- $searchstring anchor]
6950 if {$here ne {}} {
6951 $ctext see $here
6953 searchmarkvisible 1
6957 proc dosearch {} {
6958 global sstring ctext searchstring searchdirn
6960 focus $sstring
6961 $sstring icursor end
6962 set searchdirn -forwards
6963 if {$searchstring ne {}} {
6964 set sel [$ctext tag ranges sel]
6965 if {$sel ne {}} {
6966 set start "[lindex $sel 0] + 1c"
6967 } elseif {[catch {set start [$ctext index anchor]}]} {
6968 set start "@0,0"
6970 set match [$ctext search -count mlen -- $searchstring $start]
6971 $ctext tag remove sel 1.0 end
6972 if {$match eq {}} {
6973 bell
6974 return
6976 $ctext see $match
6977 set mend "$match + $mlen c"
6978 $ctext tag add sel $match $mend
6979 $ctext mark unset anchor
6983 proc dosearchback {} {
6984 global sstring ctext searchstring searchdirn
6986 focus $sstring
6987 $sstring icursor end
6988 set searchdirn -backwards
6989 if {$searchstring ne {}} {
6990 set sel [$ctext tag ranges sel]
6991 if {$sel ne {}} {
6992 set start [lindex $sel 0]
6993 } elseif {[catch {set start [$ctext index anchor]}]} {
6994 set start @0,[winfo height $ctext]
6996 set match [$ctext search -backwards -count ml -- $searchstring $start]
6997 $ctext tag remove sel 1.0 end
6998 if {$match eq {}} {
6999 bell
7000 return
7002 $ctext see $match
7003 set mend "$match + $ml c"
7004 $ctext tag add sel $match $mend
7005 $ctext mark unset anchor
7009 proc searchmark {first last} {
7010 global ctext searchstring
7012 set mend $first.0
7013 while {1} {
7014 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
7015 if {$match eq {}} break
7016 set mend "$match + $mlen c"
7017 $ctext tag add found $match $mend
7021 proc searchmarkvisible {doall} {
7022 global ctext smarktop smarkbot
7024 set topline [lindex [split [$ctext index @0,0] .] 0]
7025 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
7026 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
7027 # no overlap with previous
7028 searchmark $topline $botline
7029 set smarktop $topline
7030 set smarkbot $botline
7031 } else {
7032 if {$topline < $smarktop} {
7033 searchmark $topline [expr {$smarktop-1}]
7034 set smarktop $topline
7036 if {$botline > $smarkbot} {
7037 searchmark [expr {$smarkbot+1}] $botline
7038 set smarkbot $botline
7043 proc scrolltext {f0 f1} {
7044 global searchstring
7046 .bleft.bottom.sb set $f0 $f1
7047 if {$searchstring ne {}} {
7048 searchmarkvisible 0
7052 proc setcoords {} {
7053 global linespc charspc canvx0 canvy0
7054 global xspc1 xspc2 lthickness
7056 set linespc [font metrics mainfont -linespace]
7057 set charspc [font measure mainfont "m"]
7058 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
7059 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
7060 set lthickness [expr {int($linespc / 9) + 1}]
7061 set xspc1(0) $linespc
7062 set xspc2 $linespc
7065 proc redisplay {} {
7066 global canv
7067 global selectedline
7069 set ymax [lindex [$canv cget -scrollregion] 3]
7070 if {$ymax eq {} || $ymax == 0} return
7071 set span [$canv yview]
7072 clear_display
7073 setcanvscroll
7074 allcanvs yview moveto [lindex $span 0]
7075 drawvisible
7076 if {$selectedline ne {}} {
7077 selectline $selectedline 0
7078 allcanvs yview moveto [lindex $span 0]
7082 proc parsefont {f n} {
7083 global fontattr
7085 set fontattr($f,family) [lindex $n 0]
7086 set s [lindex $n 1]
7087 if {$s eq {} || $s == 0} {
7088 set s 10
7089 } elseif {$s < 0} {
7090 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
7092 set fontattr($f,size) $s
7093 set fontattr($f,weight) normal
7094 set fontattr($f,slant) roman
7095 foreach style [lrange $n 2 end] {
7096 switch -- $style {
7097 "normal" -
7098 "bold" {set fontattr($f,weight) $style}
7099 "roman" -
7100 "italic" {set fontattr($f,slant) $style}
7105 proc fontflags {f {isbold 0}} {
7106 global fontattr
7108 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
7109 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
7110 -slant $fontattr($f,slant)]
7113 proc fontname {f} {
7114 global fontattr
7116 set n [list $fontattr($f,family) $fontattr($f,size)]
7117 if {$fontattr($f,weight) eq "bold"} {
7118 lappend n "bold"
7120 if {$fontattr($f,slant) eq "italic"} {
7121 lappend n "italic"
7123 return $n
7126 proc incrfont {inc} {
7127 global mainfont textfont ctext canv cflist showrefstop
7128 global stopped entries fontattr
7130 unmarkmatches
7131 set s $fontattr(mainfont,size)
7132 incr s $inc
7133 if {$s < 1} {
7134 set s 1
7136 set fontattr(mainfont,size) $s
7137 font config mainfont -size $s
7138 font config mainfontbold -size $s
7139 set mainfont [fontname mainfont]
7140 set s $fontattr(textfont,size)
7141 incr s $inc
7142 if {$s < 1} {
7143 set s 1
7145 set fontattr(textfont,size) $s
7146 font config textfont -size $s
7147 font config textfontbold -size $s
7148 set textfont [fontname textfont]
7149 setcoords
7150 settabs
7151 redisplay
7154 proc clearsha1 {} {
7155 global sha1entry sha1string
7156 if {[string length $sha1string] == 40} {
7157 $sha1entry delete 0 end
7161 proc sha1change {n1 n2 op} {
7162 global sha1string currentid sha1but
7163 if {$sha1string == {}
7164 || ([info exists currentid] && $sha1string == $currentid)} {
7165 set state disabled
7166 } else {
7167 set state normal
7169 if {[$sha1but cget -state] == $state} return
7170 if {$state == "normal"} {
7171 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
7172 } else {
7173 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
7177 proc gotocommit {} {
7178 global sha1string tagids headids curview varcid
7180 if {$sha1string == {}
7181 || ([info exists currentid] && $sha1string == $currentid)} return
7182 if {[info exists tagids($sha1string)]} {
7183 set id $tagids($sha1string)
7184 } elseif {[info exists headids($sha1string)]} {
7185 set id $headids($sha1string)
7186 } else {
7187 set id [string tolower $sha1string]
7188 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7189 set matches [longid $id]
7190 if {$matches ne {}} {
7191 if {[llength $matches] > 1} {
7192 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7193 return
7195 set id [lindex $matches 0]
7199 if {[commitinview $id $curview]} {
7200 selectline [rowofcommit $id] 1
7201 return
7203 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7204 set msg [mc "SHA1 id %s is not known" $sha1string]
7205 } else {
7206 set msg [mc "Tag/Head %s is not known" $sha1string]
7208 error_popup $msg
7211 proc lineenter {x y id} {
7212 global hoverx hovery hoverid hovertimer
7213 global commitinfo canv
7215 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7216 set hoverx $x
7217 set hovery $y
7218 set hoverid $id
7219 if {[info exists hovertimer]} {
7220 after cancel $hovertimer
7222 set hovertimer [after 500 linehover]
7223 $canv delete hover
7226 proc linemotion {x y id} {
7227 global hoverx hovery hoverid hovertimer
7229 if {[info exists hoverid] && $id == $hoverid} {
7230 set hoverx $x
7231 set hovery $y
7232 if {[info exists hovertimer]} {
7233 after cancel $hovertimer
7235 set hovertimer [after 500 linehover]
7239 proc lineleave {id} {
7240 global hoverid hovertimer canv
7242 if {[info exists hoverid] && $id == $hoverid} {
7243 $canv delete hover
7244 if {[info exists hovertimer]} {
7245 after cancel $hovertimer
7246 unset hovertimer
7248 unset hoverid
7252 proc linehover {} {
7253 global hoverx hovery hoverid hovertimer
7254 global canv linespc lthickness
7255 global commitinfo
7257 set text [lindex $commitinfo($hoverid) 0]
7258 set ymax [lindex [$canv cget -scrollregion] 3]
7259 if {$ymax == {}} return
7260 set yfrac [lindex [$canv yview] 0]
7261 set x [expr {$hoverx + 2 * $linespc}]
7262 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7263 set x0 [expr {$x - 2 * $lthickness}]
7264 set y0 [expr {$y - 2 * $lthickness}]
7265 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7266 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7267 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7268 -fill \#ffff80 -outline black -width 1 -tags hover]
7269 $canv raise $t
7270 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7271 -font mainfont]
7272 $canv raise $t
7275 proc clickisonarrow {id y} {
7276 global lthickness
7278 set ranges [rowranges $id]
7279 set thresh [expr {2 * $lthickness + 6}]
7280 set n [expr {[llength $ranges] - 1}]
7281 for {set i 1} {$i < $n} {incr i} {
7282 set row [lindex $ranges $i]
7283 if {abs([yc $row] - $y) < $thresh} {
7284 return $i
7287 return {}
7290 proc arrowjump {id n y} {
7291 global canv
7293 # 1 <-> 2, 3 <-> 4, etc...
7294 set n [expr {(($n - 1) ^ 1) + 1}]
7295 set row [lindex [rowranges $id] $n]
7296 set yt [yc $row]
7297 set ymax [lindex [$canv cget -scrollregion] 3]
7298 if {$ymax eq {} || $ymax <= 0} return
7299 set view [$canv yview]
7300 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7301 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7302 if {$yfrac < 0} {
7303 set yfrac 0
7305 allcanvs yview moveto $yfrac
7308 proc lineclick {x y id isnew} {
7309 global ctext commitinfo children canv thickerline curview
7311 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7312 unmarkmatches
7313 unselectline
7314 normalline
7315 $canv delete hover
7316 # draw this line thicker than normal
7317 set thickerline $id
7318 drawlines $id
7319 if {$isnew} {
7320 set ymax [lindex [$canv cget -scrollregion] 3]
7321 if {$ymax eq {}} return
7322 set yfrac [lindex [$canv yview] 0]
7323 set y [expr {$y + $yfrac * $ymax}]
7325 set dirn [clickisonarrow $id $y]
7326 if {$dirn ne {}} {
7327 arrowjump $id $dirn $y
7328 return
7331 if {$isnew} {
7332 addtohistory [list lineclick $x $y $id 0]
7334 # fill the details pane with info about this line
7335 $ctext conf -state normal
7336 clear_ctext
7337 settabs 0
7338 $ctext insert end "[mc "Parent"]:\t"
7339 $ctext insert end $id link0
7340 setlink $id link0
7341 set info $commitinfo($id)
7342 $ctext insert end "\n\t[lindex $info 0]\n"
7343 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7344 set date [formatdate [lindex $info 2]]
7345 $ctext insert end "\t[mc "Date"]:\t$date\n"
7346 set kids $children($curview,$id)
7347 if {$kids ne {}} {
7348 $ctext insert end "\n[mc "Children"]:"
7349 set i 0
7350 foreach child $kids {
7351 incr i
7352 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7353 set info $commitinfo($child)
7354 $ctext insert end "\n\t"
7355 $ctext insert end $child link$i
7356 setlink $child link$i
7357 $ctext insert end "\n\t[lindex $info 0]"
7358 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7359 set date [formatdate [lindex $info 2]]
7360 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7363 $ctext conf -state disabled
7364 init_flist {}
7367 proc normalline {} {
7368 global thickerline
7369 if {[info exists thickerline]} {
7370 set id $thickerline
7371 unset thickerline
7372 drawlines $id
7376 proc selbyid {id} {
7377 global curview
7378 if {[commitinview $id $curview]} {
7379 selectline [rowofcommit $id] 1
7383 proc mstime {} {
7384 global startmstime
7385 if {![info exists startmstime]} {
7386 set startmstime [clock clicks -milliseconds]
7388 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7391 proc rowmenu {x y id} {
7392 global rowctxmenu selectedline rowmenuid curview
7393 global nullid nullid2 fakerowmenu mainhead
7395 stopfinding
7396 set rowmenuid $id
7397 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7398 set state disabled
7399 } else {
7400 set state normal
7402 if {$id ne $nullid && $id ne $nullid2} {
7403 set menu $rowctxmenu
7404 if {$mainhead ne {}} {
7405 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7406 } else {
7407 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7409 } else {
7410 set menu $fakerowmenu
7412 $menu entryconfigure [mca "Diff this -> selected"] -state $state
7413 $menu entryconfigure [mca "Diff selected -> this"] -state $state
7414 $menu entryconfigure [mca "Make patch"] -state $state
7415 tk_popup $menu $x $y
7418 proc diffvssel {dirn} {
7419 global rowmenuid selectedline
7421 if {$selectedline eq {}} return
7422 if {$dirn} {
7423 set oldid [commitonrow $selectedline]
7424 set newid $rowmenuid
7425 } else {
7426 set oldid $rowmenuid
7427 set newid [commitonrow $selectedline]
7429 addtohistory [list doseldiff $oldid $newid]
7430 doseldiff $oldid $newid
7433 proc doseldiff {oldid newid} {
7434 global ctext
7435 global commitinfo
7437 $ctext conf -state normal
7438 clear_ctext
7439 init_flist [mc "Top"]
7440 $ctext insert end "[mc "From"] "
7441 $ctext insert end $oldid link0
7442 setlink $oldid link0
7443 $ctext insert end "\n "
7444 $ctext insert end [lindex $commitinfo($oldid) 0]
7445 $ctext insert end "\n\n[mc "To"] "
7446 $ctext insert end $newid link1
7447 setlink $newid link1
7448 $ctext insert end "\n "
7449 $ctext insert end [lindex $commitinfo($newid) 0]
7450 $ctext insert end "\n"
7451 $ctext conf -state disabled
7452 $ctext tag remove found 1.0 end
7453 startdiff [list $oldid $newid]
7456 proc mkpatch {} {
7457 global rowmenuid currentid commitinfo patchtop patchnum
7459 if {![info exists currentid]} return
7460 set oldid $currentid
7461 set oldhead [lindex $commitinfo($oldid) 0]
7462 set newid $rowmenuid
7463 set newhead [lindex $commitinfo($newid) 0]
7464 set top .patch
7465 set patchtop $top
7466 catch {destroy $top}
7467 toplevel $top
7468 label $top.title -text [mc "Generate patch"]
7469 grid $top.title - -pady 10
7470 label $top.from -text [mc "From:"]
7471 entry $top.fromsha1 -width 40 -relief flat
7472 $top.fromsha1 insert 0 $oldid
7473 $top.fromsha1 conf -state readonly
7474 grid $top.from $top.fromsha1 -sticky w
7475 entry $top.fromhead -width 60 -relief flat
7476 $top.fromhead insert 0 $oldhead
7477 $top.fromhead conf -state readonly
7478 grid x $top.fromhead -sticky w
7479 label $top.to -text [mc "To:"]
7480 entry $top.tosha1 -width 40 -relief flat
7481 $top.tosha1 insert 0 $newid
7482 $top.tosha1 conf -state readonly
7483 grid $top.to $top.tosha1 -sticky w
7484 entry $top.tohead -width 60 -relief flat
7485 $top.tohead insert 0 $newhead
7486 $top.tohead conf -state readonly
7487 grid x $top.tohead -sticky w
7488 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7489 grid $top.rev x -pady 10
7490 label $top.flab -text [mc "Output file:"]
7491 entry $top.fname -width 60
7492 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7493 incr patchnum
7494 grid $top.flab $top.fname -sticky w
7495 frame $top.buts
7496 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7497 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7498 grid $top.buts.gen $top.buts.can
7499 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7500 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7501 grid $top.buts - -pady 10 -sticky ew
7502 focus $top.fname
7505 proc mkpatchrev {} {
7506 global patchtop
7508 set oldid [$patchtop.fromsha1 get]
7509 set oldhead [$patchtop.fromhead get]
7510 set newid [$patchtop.tosha1 get]
7511 set newhead [$patchtop.tohead get]
7512 foreach e [list fromsha1 fromhead tosha1 tohead] \
7513 v [list $newid $newhead $oldid $oldhead] {
7514 $patchtop.$e conf -state normal
7515 $patchtop.$e delete 0 end
7516 $patchtop.$e insert 0 $v
7517 $patchtop.$e conf -state readonly
7521 proc mkpatchgo {} {
7522 global patchtop nullid nullid2
7524 set oldid [$patchtop.fromsha1 get]
7525 set newid [$patchtop.tosha1 get]
7526 set fname [$patchtop.fname get]
7527 set cmd [diffcmd [list $oldid $newid] -p]
7528 # trim off the initial "|"
7529 set cmd [lrange $cmd 1 end]
7530 lappend cmd >$fname &
7531 if {[catch {eval exec $cmd} err]} {
7532 error_popup "[mc "Error creating patch:"] $err"
7534 catch {destroy $patchtop}
7535 unset patchtop
7538 proc mkpatchcan {} {
7539 global patchtop
7541 catch {destroy $patchtop}
7542 unset patchtop
7545 proc mktag {} {
7546 global rowmenuid mktagtop commitinfo
7548 set top .maketag
7549 set mktagtop $top
7550 catch {destroy $top}
7551 toplevel $top
7552 label $top.title -text [mc "Create tag"]
7553 grid $top.title - -pady 10
7554 label $top.id -text [mc "ID:"]
7555 entry $top.sha1 -width 40 -relief flat
7556 $top.sha1 insert 0 $rowmenuid
7557 $top.sha1 conf -state readonly
7558 grid $top.id $top.sha1 -sticky w
7559 entry $top.head -width 60 -relief flat
7560 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7561 $top.head conf -state readonly
7562 grid x $top.head -sticky w
7563 label $top.tlab -text [mc "Tag name:"]
7564 entry $top.tag -width 60
7565 grid $top.tlab $top.tag -sticky w
7566 frame $top.buts
7567 button $top.buts.gen -text [mc "Create"] -command mktaggo
7568 button $top.buts.can -text [mc "Cancel"] -command mktagcan
7569 grid $top.buts.gen $top.buts.can
7570 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7571 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7572 grid $top.buts - -pady 10 -sticky ew
7573 focus $top.tag
7576 proc domktag {} {
7577 global mktagtop env tagids idtags
7579 set id [$mktagtop.sha1 get]
7580 set tag [$mktagtop.tag get]
7581 if {$tag == {}} {
7582 error_popup [mc "No tag name specified"]
7583 return
7585 if {[info exists tagids($tag)]} {
7586 error_popup [mc "Tag \"%s\" already exists" $tag]
7587 return
7589 if {[catch {
7590 exec git tag $tag $id
7591 } err]} {
7592 error_popup "[mc "Error creating tag:"] $err"
7593 return
7596 set tagids($tag) $id
7597 lappend idtags($id) $tag
7598 redrawtags $id
7599 addedtag $id
7600 dispneartags 0
7601 run refill_reflist
7604 proc redrawtags {id} {
7605 global canv linehtag idpos currentid curview cmitlisted
7606 global canvxmax iddrawn circleitem mainheadid circlecolors
7608 if {![commitinview $id $curview]} return
7609 if {![info exists iddrawn($id)]} return
7610 set row [rowofcommit $id]
7611 if {$id eq $mainheadid} {
7612 set ofill yellow
7613 } else {
7614 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
7616 $canv itemconf $circleitem($row) -fill $ofill
7617 $canv delete tag.$id
7618 set xt [eval drawtags $id $idpos($id)]
7619 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
7620 set text [$canv itemcget $linehtag($row) -text]
7621 set font [$canv itemcget $linehtag($row) -font]
7622 set xr [expr {$xt + [font measure $font $text]}]
7623 if {$xr > $canvxmax} {
7624 set canvxmax $xr
7625 setcanvscroll
7627 if {[info exists currentid] && $currentid == $id} {
7628 make_secsel $row
7632 proc mktagcan {} {
7633 global mktagtop
7635 catch {destroy $mktagtop}
7636 unset mktagtop
7639 proc mktaggo {} {
7640 domktag
7641 mktagcan
7644 proc writecommit {} {
7645 global rowmenuid wrcomtop commitinfo wrcomcmd
7647 set top .writecommit
7648 set wrcomtop $top
7649 catch {destroy $top}
7650 toplevel $top
7651 label $top.title -text [mc "Write commit to file"]
7652 grid $top.title - -pady 10
7653 label $top.id -text [mc "ID:"]
7654 entry $top.sha1 -width 40 -relief flat
7655 $top.sha1 insert 0 $rowmenuid
7656 $top.sha1 conf -state readonly
7657 grid $top.id $top.sha1 -sticky w
7658 entry $top.head -width 60 -relief flat
7659 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7660 $top.head conf -state readonly
7661 grid x $top.head -sticky w
7662 label $top.clab -text [mc "Command:"]
7663 entry $top.cmd -width 60 -textvariable wrcomcmd
7664 grid $top.clab $top.cmd -sticky w -pady 10
7665 label $top.flab -text [mc "Output file:"]
7666 entry $top.fname -width 60
7667 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
7668 grid $top.flab $top.fname -sticky w
7669 frame $top.buts
7670 button $top.buts.gen -text [mc "Write"] -command wrcomgo
7671 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
7672 grid $top.buts.gen $top.buts.can
7673 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7674 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7675 grid $top.buts - -pady 10 -sticky ew
7676 focus $top.fname
7679 proc wrcomgo {} {
7680 global wrcomtop
7682 set id [$wrcomtop.sha1 get]
7683 set cmd "echo $id | [$wrcomtop.cmd get]"
7684 set fname [$wrcomtop.fname get]
7685 if {[catch {exec sh -c $cmd >$fname &} err]} {
7686 error_popup "[mc "Error writing commit:"] $err"
7688 catch {destroy $wrcomtop}
7689 unset wrcomtop
7692 proc wrcomcan {} {
7693 global wrcomtop
7695 catch {destroy $wrcomtop}
7696 unset wrcomtop
7699 proc mkbranch {} {
7700 global rowmenuid mkbrtop
7702 set top .makebranch
7703 catch {destroy $top}
7704 toplevel $top
7705 label $top.title -text [mc "Create new branch"]
7706 grid $top.title - -pady 10
7707 label $top.id -text [mc "ID:"]
7708 entry $top.sha1 -width 40 -relief flat
7709 $top.sha1 insert 0 $rowmenuid
7710 $top.sha1 conf -state readonly
7711 grid $top.id $top.sha1 -sticky w
7712 label $top.nlab -text [mc "Name:"]
7713 entry $top.name -width 40
7714 bind $top.name <Key-Return> "[list mkbrgo $top]"
7715 grid $top.nlab $top.name -sticky w
7716 frame $top.buts
7717 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
7718 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
7719 grid $top.buts.go $top.buts.can
7720 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7721 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7722 grid $top.buts - -pady 10 -sticky ew
7723 focus $top.name
7726 proc mkbrgo {top} {
7727 global headids idheads
7729 set name [$top.name get]
7730 set id [$top.sha1 get]
7731 if {$name eq {}} {
7732 error_popup [mc "Please specify a name for the new branch"]
7733 return
7735 catch {destroy $top}
7736 nowbusy newbranch
7737 update
7738 if {[catch {
7739 exec git branch $name $id
7740 } err]} {
7741 notbusy newbranch
7742 error_popup $err
7743 } else {
7744 set headids($name) $id
7745 lappend idheads($id) $name
7746 addedhead $id $name
7747 notbusy newbranch
7748 redrawtags $id
7749 dispneartags 0
7750 run refill_reflist
7754 proc cherrypick {} {
7755 global rowmenuid curview
7756 global mainhead mainheadid
7758 set oldhead [exec git rev-parse HEAD]
7759 set dheads [descheads $rowmenuid]
7760 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
7761 set ok [confirm_popup [mc "Commit %s is already\
7762 included in branch %s -- really re-apply it?" \
7763 [string range $rowmenuid 0 7] $mainhead]]
7764 if {!$ok} return
7766 nowbusy cherrypick [mc "Cherry-picking"]
7767 update
7768 # Unfortunately git-cherry-pick writes stuff to stderr even when
7769 # no error occurs, and exec takes that as an indication of error...
7770 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
7771 notbusy cherrypick
7772 error_popup $err
7773 return
7775 set newhead [exec git rev-parse HEAD]
7776 if {$newhead eq $oldhead} {
7777 notbusy cherrypick
7778 error_popup [mc "No changes committed"]
7779 return
7781 addnewchild $newhead $oldhead
7782 if {[commitinview $oldhead $curview]} {
7783 insertrow $newhead $oldhead $curview
7784 if {$mainhead ne {}} {
7785 movehead $newhead $mainhead
7786 movedhead $newhead $mainhead
7788 set mainheadid $newhead
7789 redrawtags $oldhead
7790 redrawtags $newhead
7791 selbyid $newhead
7793 notbusy cherrypick
7796 proc resethead {} {
7797 global mainhead rowmenuid confirm_ok resettype
7799 set confirm_ok 0
7800 set w ".confirmreset"
7801 toplevel $w
7802 wm transient $w .
7803 wm title $w [mc "Confirm reset"]
7804 message $w.m -text \
7805 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
7806 -justify center -aspect 1000
7807 pack $w.m -side top -fill x -padx 20 -pady 20
7808 frame $w.f -relief sunken -border 2
7809 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
7810 grid $w.f.rt -sticky w
7811 set resettype mixed
7812 radiobutton $w.f.soft -value soft -variable resettype -justify left \
7813 -text [mc "Soft: Leave working tree and index untouched"]
7814 grid $w.f.soft -sticky w
7815 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
7816 -text [mc "Mixed: Leave working tree untouched, reset index"]
7817 grid $w.f.mixed -sticky w
7818 radiobutton $w.f.hard -value hard -variable resettype -justify left \
7819 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
7820 grid $w.f.hard -sticky w
7821 pack $w.f -side top -fill x
7822 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
7823 pack $w.ok -side left -fill x -padx 20 -pady 20
7824 button $w.cancel -text [mc Cancel] -command "destroy $w"
7825 pack $w.cancel -side right -fill x -padx 20 -pady 20
7826 bind $w <Visibility> "grab $w; focus $w"
7827 tkwait window $w
7828 if {!$confirm_ok} return
7829 if {[catch {set fd [open \
7830 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
7831 error_popup $err
7832 } else {
7833 dohidelocalchanges
7834 filerun $fd [list readresetstat $fd]
7835 nowbusy reset [mc "Resetting"]
7836 selbyid $rowmenuid
7840 proc readresetstat {fd} {
7841 global mainhead mainheadid showlocalchanges rprogcoord
7843 if {[gets $fd line] >= 0} {
7844 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7845 set rprogcoord [expr {1.0 * $m / $n}]
7846 adjustprogress
7848 return 1
7850 set rprogcoord 0
7851 adjustprogress
7852 notbusy reset
7853 if {[catch {close $fd} err]} {
7854 error_popup $err
7856 set oldhead $mainheadid
7857 set newhead [exec git rev-parse HEAD]
7858 if {$newhead ne $oldhead} {
7859 movehead $newhead $mainhead
7860 movedhead $newhead $mainhead
7861 set mainheadid $newhead
7862 redrawtags $oldhead
7863 redrawtags $newhead
7865 if {$showlocalchanges} {
7866 doshowlocalchanges
7868 return 0
7871 # context menu for a head
7872 proc headmenu {x y id head} {
7873 global headmenuid headmenuhead headctxmenu mainhead
7875 stopfinding
7876 set headmenuid $id
7877 set headmenuhead $head
7878 set state normal
7879 if {$head eq $mainhead} {
7880 set state disabled
7882 $headctxmenu entryconfigure 0 -state $state
7883 $headctxmenu entryconfigure 1 -state $state
7884 tk_popup $headctxmenu $x $y
7887 proc cobranch {} {
7888 global headmenuid headmenuhead headids
7889 global showlocalchanges mainheadid
7891 # check the tree is clean first??
7892 nowbusy checkout [mc "Checking out"]
7893 update
7894 dohidelocalchanges
7895 if {[catch {
7896 set fd [open [list | git checkout $headmenuhead 2>@1] r]
7897 } err]} {
7898 notbusy checkout
7899 error_popup $err
7900 if {$showlocalchanges} {
7901 dodiffindex
7903 } else {
7904 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
7908 proc readcheckoutstat {fd newhead newheadid} {
7909 global mainhead mainheadid headids showlocalchanges progresscoords
7911 if {[gets $fd line] >= 0} {
7912 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7913 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
7914 adjustprogress
7916 return 1
7918 set progresscoords {0 0}
7919 adjustprogress
7920 notbusy checkout
7921 if {[catch {close $fd} err]} {
7922 error_popup $err
7924 set oldmainid $mainheadid
7925 set mainhead $newhead
7926 set mainheadid $newheadid
7927 redrawtags $oldmainid
7928 redrawtags $newheadid
7929 selbyid $newheadid
7930 if {$showlocalchanges} {
7931 dodiffindex
7935 proc rmbranch {} {
7936 global headmenuid headmenuhead mainhead
7937 global idheads
7939 set head $headmenuhead
7940 set id $headmenuid
7941 # this check shouldn't be needed any more...
7942 if {$head eq $mainhead} {
7943 error_popup [mc "Cannot delete the currently checked-out branch"]
7944 return
7946 set dheads [descheads $id]
7947 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
7948 # the stuff on this branch isn't on any other branch
7949 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
7950 branch.\nReally delete branch %s?" $head $head]]} return
7952 nowbusy rmbranch
7953 update
7954 if {[catch {exec git branch -D $head} err]} {
7955 notbusy rmbranch
7956 error_popup $err
7957 return
7959 removehead $id $head
7960 removedhead $id $head
7961 redrawtags $id
7962 notbusy rmbranch
7963 dispneartags 0
7964 run refill_reflist
7967 # Display a list of tags and heads
7968 proc showrefs {} {
7969 global showrefstop bgcolor fgcolor selectbgcolor
7970 global bglist fglist reflistfilter reflist maincursor
7972 set top .showrefs
7973 set showrefstop $top
7974 if {[winfo exists $top]} {
7975 raise $top
7976 refill_reflist
7977 return
7979 toplevel $top
7980 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
7981 text $top.list -background $bgcolor -foreground $fgcolor \
7982 -selectbackground $selectbgcolor -font mainfont \
7983 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
7984 -width 30 -height 20 -cursor $maincursor \
7985 -spacing1 1 -spacing3 1 -state disabled
7986 $top.list tag configure highlight -background $selectbgcolor
7987 lappend bglist $top.list
7988 lappend fglist $top.list
7989 scrollbar $top.ysb -command "$top.list yview" -orient vertical
7990 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
7991 grid $top.list $top.ysb -sticky nsew
7992 grid $top.xsb x -sticky ew
7993 frame $top.f
7994 label $top.f.l -text "[mc "Filter"]: "
7995 entry $top.f.e -width 20 -textvariable reflistfilter
7996 set reflistfilter "*"
7997 trace add variable reflistfilter write reflistfilter_change
7998 pack $top.f.e -side right -fill x -expand 1
7999 pack $top.f.l -side left
8000 grid $top.f - -sticky ew -pady 2
8001 button $top.close -command [list destroy $top] -text [mc "Close"]
8002 grid $top.close -
8003 grid columnconfigure $top 0 -weight 1
8004 grid rowconfigure $top 0 -weight 1
8005 bind $top.list <1> {break}
8006 bind $top.list <B1-Motion> {break}
8007 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
8008 set reflist {}
8009 refill_reflist
8012 proc sel_reflist {w x y} {
8013 global showrefstop reflist headids tagids otherrefids
8015 if {![winfo exists $showrefstop]} return
8016 set l [lindex [split [$w index "@$x,$y"] "."] 0]
8017 set ref [lindex $reflist [expr {$l-1}]]
8018 set n [lindex $ref 0]
8019 switch -- [lindex $ref 1] {
8020 "H" {selbyid $headids($n)}
8021 "T" {selbyid $tagids($n)}
8022 "o" {selbyid $otherrefids($n)}
8024 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
8027 proc unsel_reflist {} {
8028 global showrefstop
8030 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8031 $showrefstop.list tag remove highlight 0.0 end
8034 proc reflistfilter_change {n1 n2 op} {
8035 global reflistfilter
8037 after cancel refill_reflist
8038 after 200 refill_reflist
8041 proc refill_reflist {} {
8042 global reflist reflistfilter showrefstop headids tagids otherrefids
8043 global curview
8045 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8046 set refs {}
8047 foreach n [array names headids] {
8048 if {[string match $reflistfilter $n]} {
8049 if {[commitinview $headids($n) $curview]} {
8050 lappend refs [list $n H]
8051 } else {
8052 interestedin $headids($n) {run refill_reflist}
8056 foreach n [array names tagids] {
8057 if {[string match $reflistfilter $n]} {
8058 if {[commitinview $tagids($n) $curview]} {
8059 lappend refs [list $n T]
8060 } else {
8061 interestedin $tagids($n) {run refill_reflist}
8065 foreach n [array names otherrefids] {
8066 if {[string match $reflistfilter $n]} {
8067 if {[commitinview $otherrefids($n) $curview]} {
8068 lappend refs [list $n o]
8069 } else {
8070 interestedin $otherrefids($n) {run refill_reflist}
8074 set refs [lsort -index 0 $refs]
8075 if {$refs eq $reflist} return
8077 # Update the contents of $showrefstop.list according to the
8078 # differences between $reflist (old) and $refs (new)
8079 $showrefstop.list conf -state normal
8080 $showrefstop.list insert end "\n"
8081 set i 0
8082 set j 0
8083 while {$i < [llength $reflist] || $j < [llength $refs]} {
8084 if {$i < [llength $reflist]} {
8085 if {$j < [llength $refs]} {
8086 set cmp [string compare [lindex $reflist $i 0] \
8087 [lindex $refs $j 0]]
8088 if {$cmp == 0} {
8089 set cmp [string compare [lindex $reflist $i 1] \
8090 [lindex $refs $j 1]]
8092 } else {
8093 set cmp -1
8095 } else {
8096 set cmp 1
8098 switch -- $cmp {
8099 -1 {
8100 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
8101 incr i
8104 incr i
8105 incr j
8108 set l [expr {$j + 1}]
8109 $showrefstop.list image create $l.0 -align baseline \
8110 -image reficon-[lindex $refs $j 1] -padx 2
8111 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
8112 incr j
8116 set reflist $refs
8117 # delete last newline
8118 $showrefstop.list delete end-2c end-1c
8119 $showrefstop.list conf -state disabled
8122 # Stuff for finding nearby tags
8123 proc getallcommits {} {
8124 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
8125 global idheads idtags idotherrefs allparents tagobjid
8127 if {![info exists allcommits]} {
8128 set nextarc 0
8129 set allcommits 0
8130 set seeds {}
8131 set allcwait 0
8132 set cachedarcs 0
8133 set allccache [file join [gitdir] "gitk.cache"]
8134 if {![catch {
8135 set f [open $allccache r]
8136 set allcwait 1
8137 getcache $f
8138 }]} return
8141 if {$allcwait} {
8142 return
8144 set cmd [list | git rev-list --parents]
8145 set allcupdate [expr {$seeds ne {}}]
8146 if {!$allcupdate} {
8147 set ids "--all"
8148 } else {
8149 set refs [concat [array names idheads] [array names idtags] \
8150 [array names idotherrefs]]
8151 set ids {}
8152 set tagobjs {}
8153 foreach name [array names tagobjid] {
8154 lappend tagobjs $tagobjid($name)
8156 foreach id [lsort -unique $refs] {
8157 if {![info exists allparents($id)] &&
8158 [lsearch -exact $tagobjs $id] < 0} {
8159 lappend ids $id
8162 if {$ids ne {}} {
8163 foreach id $seeds {
8164 lappend ids "^$id"
8168 if {$ids ne {}} {
8169 set fd [open [concat $cmd $ids] r]
8170 fconfigure $fd -blocking 0
8171 incr allcommits
8172 nowbusy allcommits
8173 filerun $fd [list getallclines $fd]
8174 } else {
8175 dispneartags 0
8179 # Since most commits have 1 parent and 1 child, we group strings of
8180 # such commits into "arcs" joining branch/merge points (BMPs), which
8181 # are commits that either don't have 1 parent or don't have 1 child.
8183 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8184 # arcout(id) - outgoing arcs for BMP
8185 # arcids(a) - list of IDs on arc including end but not start
8186 # arcstart(a) - BMP ID at start of arc
8187 # arcend(a) - BMP ID at end of arc
8188 # growing(a) - arc a is still growing
8189 # arctags(a) - IDs out of arcids (excluding end) that have tags
8190 # archeads(a) - IDs out of arcids (excluding end) that have heads
8191 # The start of an arc is at the descendent end, so "incoming" means
8192 # coming from descendents, and "outgoing" means going towards ancestors.
8194 proc getallclines {fd} {
8195 global allparents allchildren idtags idheads nextarc
8196 global arcnos arcids arctags arcout arcend arcstart archeads growing
8197 global seeds allcommits cachedarcs allcupdate
8199 set nid 0
8200 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8201 set id [lindex $line 0]
8202 if {[info exists allparents($id)]} {
8203 # seen it already
8204 continue
8206 set cachedarcs 0
8207 set olds [lrange $line 1 end]
8208 set allparents($id) $olds
8209 if {![info exists allchildren($id)]} {
8210 set allchildren($id) {}
8211 set arcnos($id) {}
8212 lappend seeds $id
8213 } else {
8214 set a $arcnos($id)
8215 if {[llength $olds] == 1 && [llength $a] == 1} {
8216 lappend arcids($a) $id
8217 if {[info exists idtags($id)]} {
8218 lappend arctags($a) $id
8220 if {[info exists idheads($id)]} {
8221 lappend archeads($a) $id
8223 if {[info exists allparents($olds)]} {
8224 # seen parent already
8225 if {![info exists arcout($olds)]} {
8226 splitarc $olds
8228 lappend arcids($a) $olds
8229 set arcend($a) $olds
8230 unset growing($a)
8232 lappend allchildren($olds) $id
8233 lappend arcnos($olds) $a
8234 continue
8237 foreach a $arcnos($id) {
8238 lappend arcids($a) $id
8239 set arcend($a) $id
8240 unset growing($a)
8243 set ao {}
8244 foreach p $olds {
8245 lappend allchildren($p) $id
8246 set a [incr nextarc]
8247 set arcstart($a) $id
8248 set archeads($a) {}
8249 set arctags($a) {}
8250 set archeads($a) {}
8251 set arcids($a) {}
8252 lappend ao $a
8253 set growing($a) 1
8254 if {[info exists allparents($p)]} {
8255 # seen it already, may need to make a new branch
8256 if {![info exists arcout($p)]} {
8257 splitarc $p
8259 lappend arcids($a) $p
8260 set arcend($a) $p
8261 unset growing($a)
8263 lappend arcnos($p) $a
8265 set arcout($id) $ao
8267 if {$nid > 0} {
8268 global cached_dheads cached_dtags cached_atags
8269 catch {unset cached_dheads}
8270 catch {unset cached_dtags}
8271 catch {unset cached_atags}
8273 if {![eof $fd]} {
8274 return [expr {$nid >= 1000? 2: 1}]
8276 set cacheok 1
8277 if {[catch {
8278 fconfigure $fd -blocking 1
8279 close $fd
8280 } err]} {
8281 # got an error reading the list of commits
8282 # if we were updating, try rereading the whole thing again
8283 if {$allcupdate} {
8284 incr allcommits -1
8285 dropcache $err
8286 return
8288 error_popup "[mc "Error reading commit topology information;\
8289 branch and preceding/following tag information\
8290 will be incomplete."]\n($err)"
8291 set cacheok 0
8293 if {[incr allcommits -1] == 0} {
8294 notbusy allcommits
8295 if {$cacheok} {
8296 run savecache
8299 dispneartags 0
8300 return 0
8303 proc recalcarc {a} {
8304 global arctags archeads arcids idtags idheads
8306 set at {}
8307 set ah {}
8308 foreach id [lrange $arcids($a) 0 end-1] {
8309 if {[info exists idtags($id)]} {
8310 lappend at $id
8312 if {[info exists idheads($id)]} {
8313 lappend ah $id
8316 set arctags($a) $at
8317 set archeads($a) $ah
8320 proc splitarc {p} {
8321 global arcnos arcids nextarc arctags archeads idtags idheads
8322 global arcstart arcend arcout allparents growing
8324 set a $arcnos($p)
8325 if {[llength $a] != 1} {
8326 puts "oops splitarc called but [llength $a] arcs already"
8327 return
8329 set a [lindex $a 0]
8330 set i [lsearch -exact $arcids($a) $p]
8331 if {$i < 0} {
8332 puts "oops splitarc $p not in arc $a"
8333 return
8335 set na [incr nextarc]
8336 if {[info exists arcend($a)]} {
8337 set arcend($na) $arcend($a)
8338 } else {
8339 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8340 set j [lsearch -exact $arcnos($l) $a]
8341 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8343 set tail [lrange $arcids($a) [expr {$i+1}] end]
8344 set arcids($a) [lrange $arcids($a) 0 $i]
8345 set arcend($a) $p
8346 set arcstart($na) $p
8347 set arcout($p) $na
8348 set arcids($na) $tail
8349 if {[info exists growing($a)]} {
8350 set growing($na) 1
8351 unset growing($a)
8354 foreach id $tail {
8355 if {[llength $arcnos($id)] == 1} {
8356 set arcnos($id) $na
8357 } else {
8358 set j [lsearch -exact $arcnos($id) $a]
8359 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8363 # reconstruct tags and heads lists
8364 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8365 recalcarc $a
8366 recalcarc $na
8367 } else {
8368 set arctags($na) {}
8369 set archeads($na) {}
8373 # Update things for a new commit added that is a child of one
8374 # existing commit. Used when cherry-picking.
8375 proc addnewchild {id p} {
8376 global allparents allchildren idtags nextarc
8377 global arcnos arcids arctags arcout arcend arcstart archeads growing
8378 global seeds allcommits
8380 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8381 set allparents($id) [list $p]
8382 set allchildren($id) {}
8383 set arcnos($id) {}
8384 lappend seeds $id
8385 lappend allchildren($p) $id
8386 set a [incr nextarc]
8387 set arcstart($a) $id
8388 set archeads($a) {}
8389 set arctags($a) {}
8390 set arcids($a) [list $p]
8391 set arcend($a) $p
8392 if {![info exists arcout($p)]} {
8393 splitarc $p
8395 lappend arcnos($p) $a
8396 set arcout($id) [list $a]
8399 # This implements a cache for the topology information.
8400 # The cache saves, for each arc, the start and end of the arc,
8401 # the ids on the arc, and the outgoing arcs from the end.
8402 proc readcache {f} {
8403 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8404 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8405 global allcwait
8407 set a $nextarc
8408 set lim $cachedarcs
8409 if {$lim - $a > 500} {
8410 set lim [expr {$a + 500}]
8412 if {[catch {
8413 if {$a == $lim} {
8414 # finish reading the cache and setting up arctags, etc.
8415 set line [gets $f]
8416 if {$line ne "1"} {error "bad final version"}
8417 close $f
8418 foreach id [array names idtags] {
8419 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8420 [llength $allparents($id)] == 1} {
8421 set a [lindex $arcnos($id) 0]
8422 if {$arctags($a) eq {}} {
8423 recalcarc $a
8427 foreach id [array names idheads] {
8428 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8429 [llength $allparents($id)] == 1} {
8430 set a [lindex $arcnos($id) 0]
8431 if {$archeads($a) eq {}} {
8432 recalcarc $a
8436 foreach id [lsort -unique $possible_seeds] {
8437 if {$arcnos($id) eq {}} {
8438 lappend seeds $id
8441 set allcwait 0
8442 } else {
8443 while {[incr a] <= $lim} {
8444 set line [gets $f]
8445 if {[llength $line] != 3} {error "bad line"}
8446 set s [lindex $line 0]
8447 set arcstart($a) $s
8448 lappend arcout($s) $a
8449 if {![info exists arcnos($s)]} {
8450 lappend possible_seeds $s
8451 set arcnos($s) {}
8453 set e [lindex $line 1]
8454 if {$e eq {}} {
8455 set growing($a) 1
8456 } else {
8457 set arcend($a) $e
8458 if {![info exists arcout($e)]} {
8459 set arcout($e) {}
8462 set arcids($a) [lindex $line 2]
8463 foreach id $arcids($a) {
8464 lappend allparents($s) $id
8465 set s $id
8466 lappend arcnos($id) $a
8468 if {![info exists allparents($s)]} {
8469 set allparents($s) {}
8471 set arctags($a) {}
8472 set archeads($a) {}
8474 set nextarc [expr {$a - 1}]
8476 } err]} {
8477 dropcache $err
8478 return 0
8480 if {!$allcwait} {
8481 getallcommits
8483 return $allcwait
8486 proc getcache {f} {
8487 global nextarc cachedarcs possible_seeds
8489 if {[catch {
8490 set line [gets $f]
8491 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
8492 # make sure it's an integer
8493 set cachedarcs [expr {int([lindex $line 1])}]
8494 if {$cachedarcs < 0} {error "bad number of arcs"}
8495 set nextarc 0
8496 set possible_seeds {}
8497 run readcache $f
8498 } err]} {
8499 dropcache $err
8501 return 0
8504 proc dropcache {err} {
8505 global allcwait nextarc cachedarcs seeds
8507 #puts "dropping cache ($err)"
8508 foreach v {arcnos arcout arcids arcstart arcend growing \
8509 arctags archeads allparents allchildren} {
8510 global $v
8511 catch {unset $v}
8513 set allcwait 0
8514 set nextarc 0
8515 set cachedarcs 0
8516 set seeds {}
8517 getallcommits
8520 proc writecache {f} {
8521 global cachearc cachedarcs allccache
8522 global arcstart arcend arcnos arcids arcout
8524 set a $cachearc
8525 set lim $cachedarcs
8526 if {$lim - $a > 1000} {
8527 set lim [expr {$a + 1000}]
8529 if {[catch {
8530 while {[incr a] <= $lim} {
8531 if {[info exists arcend($a)]} {
8532 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
8533 } else {
8534 puts $f [list $arcstart($a) {} $arcids($a)]
8537 } err]} {
8538 catch {close $f}
8539 catch {file delete $allccache}
8540 #puts "writing cache failed ($err)"
8541 return 0
8543 set cachearc [expr {$a - 1}]
8544 if {$a > $cachedarcs} {
8545 puts $f "1"
8546 close $f
8547 return 0
8549 return 1
8552 proc savecache {} {
8553 global nextarc cachedarcs cachearc allccache
8555 if {$nextarc == $cachedarcs} return
8556 set cachearc 0
8557 set cachedarcs $nextarc
8558 catch {
8559 set f [open $allccache w]
8560 puts $f [list 1 $cachedarcs]
8561 run writecache $f
8565 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
8566 # or 0 if neither is true.
8567 proc anc_or_desc {a b} {
8568 global arcout arcstart arcend arcnos cached_isanc
8570 if {$arcnos($a) eq $arcnos($b)} {
8571 # Both are on the same arc(s); either both are the same BMP,
8572 # or if one is not a BMP, the other is also not a BMP or is
8573 # the BMP at end of the arc (and it only has 1 incoming arc).
8574 # Or both can be BMPs with no incoming arcs.
8575 if {$a eq $b || $arcnos($a) eq {}} {
8576 return 0
8578 # assert {[llength $arcnos($a)] == 1}
8579 set arc [lindex $arcnos($a) 0]
8580 set i [lsearch -exact $arcids($arc) $a]
8581 set j [lsearch -exact $arcids($arc) $b]
8582 if {$i < 0 || $i > $j} {
8583 return 1
8584 } else {
8585 return -1
8589 if {![info exists arcout($a)]} {
8590 set arc [lindex $arcnos($a) 0]
8591 if {[info exists arcend($arc)]} {
8592 set aend $arcend($arc)
8593 } else {
8594 set aend {}
8596 set a $arcstart($arc)
8597 } else {
8598 set aend $a
8600 if {![info exists arcout($b)]} {
8601 set arc [lindex $arcnos($b) 0]
8602 if {[info exists arcend($arc)]} {
8603 set bend $arcend($arc)
8604 } else {
8605 set bend {}
8607 set b $arcstart($arc)
8608 } else {
8609 set bend $b
8611 if {$a eq $bend} {
8612 return 1
8614 if {$b eq $aend} {
8615 return -1
8617 if {[info exists cached_isanc($a,$bend)]} {
8618 if {$cached_isanc($a,$bend)} {
8619 return 1
8622 if {[info exists cached_isanc($b,$aend)]} {
8623 if {$cached_isanc($b,$aend)} {
8624 return -1
8626 if {[info exists cached_isanc($a,$bend)]} {
8627 return 0
8631 set todo [list $a $b]
8632 set anc($a) a
8633 set anc($b) b
8634 for {set i 0} {$i < [llength $todo]} {incr i} {
8635 set x [lindex $todo $i]
8636 if {$anc($x) eq {}} {
8637 continue
8639 foreach arc $arcnos($x) {
8640 set xd $arcstart($arc)
8641 if {$xd eq $bend} {
8642 set cached_isanc($a,$bend) 1
8643 set cached_isanc($b,$aend) 0
8644 return 1
8645 } elseif {$xd eq $aend} {
8646 set cached_isanc($b,$aend) 1
8647 set cached_isanc($a,$bend) 0
8648 return -1
8650 if {![info exists anc($xd)]} {
8651 set anc($xd) $anc($x)
8652 lappend todo $xd
8653 } elseif {$anc($xd) ne $anc($x)} {
8654 set anc($xd) {}
8658 set cached_isanc($a,$bend) 0
8659 set cached_isanc($b,$aend) 0
8660 return 0
8663 # This identifies whether $desc has an ancestor that is
8664 # a growing tip of the graph and which is not an ancestor of $anc
8665 # and returns 0 if so and 1 if not.
8666 # If we subsequently discover a tag on such a growing tip, and that
8667 # turns out to be a descendent of $anc (which it could, since we
8668 # don't necessarily see children before parents), then $desc
8669 # isn't a good choice to display as a descendent tag of
8670 # $anc (since it is the descendent of another tag which is
8671 # a descendent of $anc). Similarly, $anc isn't a good choice to
8672 # display as a ancestor tag of $desc.
8674 proc is_certain {desc anc} {
8675 global arcnos arcout arcstart arcend growing problems
8677 set certain {}
8678 if {[llength $arcnos($anc)] == 1} {
8679 # tags on the same arc are certain
8680 if {$arcnos($desc) eq $arcnos($anc)} {
8681 return 1
8683 if {![info exists arcout($anc)]} {
8684 # if $anc is partway along an arc, use the start of the arc instead
8685 set a [lindex $arcnos($anc) 0]
8686 set anc $arcstart($a)
8689 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
8690 set x $desc
8691 } else {
8692 set a [lindex $arcnos($desc) 0]
8693 set x $arcend($a)
8695 if {$x == $anc} {
8696 return 1
8698 set anclist [list $x]
8699 set dl($x) 1
8700 set nnh 1
8701 set ngrowanc 0
8702 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
8703 set x [lindex $anclist $i]
8704 if {$dl($x)} {
8705 incr nnh -1
8707 set done($x) 1
8708 foreach a $arcout($x) {
8709 if {[info exists growing($a)]} {
8710 if {![info exists growanc($x)] && $dl($x)} {
8711 set growanc($x) 1
8712 incr ngrowanc
8714 } else {
8715 set y $arcend($a)
8716 if {[info exists dl($y)]} {
8717 if {$dl($y)} {
8718 if {!$dl($x)} {
8719 set dl($y) 0
8720 if {![info exists done($y)]} {
8721 incr nnh -1
8723 if {[info exists growanc($x)]} {
8724 incr ngrowanc -1
8726 set xl [list $y]
8727 for {set k 0} {$k < [llength $xl]} {incr k} {
8728 set z [lindex $xl $k]
8729 foreach c $arcout($z) {
8730 if {[info exists arcend($c)]} {
8731 set v $arcend($c)
8732 if {[info exists dl($v)] && $dl($v)} {
8733 set dl($v) 0
8734 if {![info exists done($v)]} {
8735 incr nnh -1
8737 if {[info exists growanc($v)]} {
8738 incr ngrowanc -1
8740 lappend xl $v
8747 } elseif {$y eq $anc || !$dl($x)} {
8748 set dl($y) 0
8749 lappend anclist $y
8750 } else {
8751 set dl($y) 1
8752 lappend anclist $y
8753 incr nnh
8758 foreach x [array names growanc] {
8759 if {$dl($x)} {
8760 return 0
8762 return 0
8764 return 1
8767 proc validate_arctags {a} {
8768 global arctags idtags
8770 set i -1
8771 set na $arctags($a)
8772 foreach id $arctags($a) {
8773 incr i
8774 if {![info exists idtags($id)]} {
8775 set na [lreplace $na $i $i]
8776 incr i -1
8779 set arctags($a) $na
8782 proc validate_archeads {a} {
8783 global archeads idheads
8785 set i -1
8786 set na $archeads($a)
8787 foreach id $archeads($a) {
8788 incr i
8789 if {![info exists idheads($id)]} {
8790 set na [lreplace $na $i $i]
8791 incr i -1
8794 set archeads($a) $na
8797 # Return the list of IDs that have tags that are descendents of id,
8798 # ignoring IDs that are descendents of IDs already reported.
8799 proc desctags {id} {
8800 global arcnos arcstart arcids arctags idtags allparents
8801 global growing cached_dtags
8803 if {![info exists allparents($id)]} {
8804 return {}
8806 set t1 [clock clicks -milliseconds]
8807 set argid $id
8808 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8809 # part-way along an arc; check that arc first
8810 set a [lindex $arcnos($id) 0]
8811 if {$arctags($a) ne {}} {
8812 validate_arctags $a
8813 set i [lsearch -exact $arcids($a) $id]
8814 set tid {}
8815 foreach t $arctags($a) {
8816 set j [lsearch -exact $arcids($a) $t]
8817 if {$j >= $i} break
8818 set tid $t
8820 if {$tid ne {}} {
8821 return $tid
8824 set id $arcstart($a)
8825 if {[info exists idtags($id)]} {
8826 return $id
8829 if {[info exists cached_dtags($id)]} {
8830 return $cached_dtags($id)
8833 set origid $id
8834 set todo [list $id]
8835 set queued($id) 1
8836 set nc 1
8837 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8838 set id [lindex $todo $i]
8839 set done($id) 1
8840 set ta [info exists hastaggedancestor($id)]
8841 if {!$ta} {
8842 incr nc -1
8844 # ignore tags on starting node
8845 if {!$ta && $i > 0} {
8846 if {[info exists idtags($id)]} {
8847 set tagloc($id) $id
8848 set ta 1
8849 } elseif {[info exists cached_dtags($id)]} {
8850 set tagloc($id) $cached_dtags($id)
8851 set ta 1
8854 foreach a $arcnos($id) {
8855 set d $arcstart($a)
8856 if {!$ta && $arctags($a) ne {}} {
8857 validate_arctags $a
8858 if {$arctags($a) ne {}} {
8859 lappend tagloc($id) [lindex $arctags($a) end]
8862 if {$ta || $arctags($a) ne {}} {
8863 set tomark [list $d]
8864 for {set j 0} {$j < [llength $tomark]} {incr j} {
8865 set dd [lindex $tomark $j]
8866 if {![info exists hastaggedancestor($dd)]} {
8867 if {[info exists done($dd)]} {
8868 foreach b $arcnos($dd) {
8869 lappend tomark $arcstart($b)
8871 if {[info exists tagloc($dd)]} {
8872 unset tagloc($dd)
8874 } elseif {[info exists queued($dd)]} {
8875 incr nc -1
8877 set hastaggedancestor($dd) 1
8881 if {![info exists queued($d)]} {
8882 lappend todo $d
8883 set queued($d) 1
8884 if {![info exists hastaggedancestor($d)]} {
8885 incr nc
8890 set tags {}
8891 foreach id [array names tagloc] {
8892 if {![info exists hastaggedancestor($id)]} {
8893 foreach t $tagloc($id) {
8894 if {[lsearch -exact $tags $t] < 0} {
8895 lappend tags $t
8900 set t2 [clock clicks -milliseconds]
8901 set loopix $i
8903 # remove tags that are descendents of other tags
8904 for {set i 0} {$i < [llength $tags]} {incr i} {
8905 set a [lindex $tags $i]
8906 for {set j 0} {$j < $i} {incr j} {
8907 set b [lindex $tags $j]
8908 set r [anc_or_desc $a $b]
8909 if {$r == 1} {
8910 set tags [lreplace $tags $j $j]
8911 incr j -1
8912 incr i -1
8913 } elseif {$r == -1} {
8914 set tags [lreplace $tags $i $i]
8915 incr i -1
8916 break
8921 if {[array names growing] ne {}} {
8922 # graph isn't finished, need to check if any tag could get
8923 # eclipsed by another tag coming later. Simply ignore any
8924 # tags that could later get eclipsed.
8925 set ctags {}
8926 foreach t $tags {
8927 if {[is_certain $t $origid]} {
8928 lappend ctags $t
8931 if {$tags eq $ctags} {
8932 set cached_dtags($origid) $tags
8933 } else {
8934 set tags $ctags
8936 } else {
8937 set cached_dtags($origid) $tags
8939 set t3 [clock clicks -milliseconds]
8940 if {0 && $t3 - $t1 >= 100} {
8941 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
8942 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8944 return $tags
8947 proc anctags {id} {
8948 global arcnos arcids arcout arcend arctags idtags allparents
8949 global growing cached_atags
8951 if {![info exists allparents($id)]} {
8952 return {}
8954 set t1 [clock clicks -milliseconds]
8955 set argid $id
8956 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8957 # part-way along an arc; check that arc first
8958 set a [lindex $arcnos($id) 0]
8959 if {$arctags($a) ne {}} {
8960 validate_arctags $a
8961 set i [lsearch -exact $arcids($a) $id]
8962 foreach t $arctags($a) {
8963 set j [lsearch -exact $arcids($a) $t]
8964 if {$j > $i} {
8965 return $t
8969 if {![info exists arcend($a)]} {
8970 return {}
8972 set id $arcend($a)
8973 if {[info exists idtags($id)]} {
8974 return $id
8977 if {[info exists cached_atags($id)]} {
8978 return $cached_atags($id)
8981 set origid $id
8982 set todo [list $id]
8983 set queued($id) 1
8984 set taglist {}
8985 set nc 1
8986 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8987 set id [lindex $todo $i]
8988 set done($id) 1
8989 set td [info exists hastaggeddescendent($id)]
8990 if {!$td} {
8991 incr nc -1
8993 # ignore tags on starting node
8994 if {!$td && $i > 0} {
8995 if {[info exists idtags($id)]} {
8996 set tagloc($id) $id
8997 set td 1
8998 } elseif {[info exists cached_atags($id)]} {
8999 set tagloc($id) $cached_atags($id)
9000 set td 1
9003 foreach a $arcout($id) {
9004 if {!$td && $arctags($a) ne {}} {
9005 validate_arctags $a
9006 if {$arctags($a) ne {}} {
9007 lappend tagloc($id) [lindex $arctags($a) 0]
9010 if {![info exists arcend($a)]} continue
9011 set d $arcend($a)
9012 if {$td || $arctags($a) ne {}} {
9013 set tomark [list $d]
9014 for {set j 0} {$j < [llength $tomark]} {incr j} {
9015 set dd [lindex $tomark $j]
9016 if {![info exists hastaggeddescendent($dd)]} {
9017 if {[info exists done($dd)]} {
9018 foreach b $arcout($dd) {
9019 if {[info exists arcend($b)]} {
9020 lappend tomark $arcend($b)
9023 if {[info exists tagloc($dd)]} {
9024 unset tagloc($dd)
9026 } elseif {[info exists queued($dd)]} {
9027 incr nc -1
9029 set hastaggeddescendent($dd) 1
9033 if {![info exists queued($d)]} {
9034 lappend todo $d
9035 set queued($d) 1
9036 if {![info exists hastaggeddescendent($d)]} {
9037 incr nc
9042 set t2 [clock clicks -milliseconds]
9043 set loopix $i
9044 set tags {}
9045 foreach id [array names tagloc] {
9046 if {![info exists hastaggeddescendent($id)]} {
9047 foreach t $tagloc($id) {
9048 if {[lsearch -exact $tags $t] < 0} {
9049 lappend tags $t
9055 # remove tags that are ancestors of other tags
9056 for {set i 0} {$i < [llength $tags]} {incr i} {
9057 set a [lindex $tags $i]
9058 for {set j 0} {$j < $i} {incr j} {
9059 set b [lindex $tags $j]
9060 set r [anc_or_desc $a $b]
9061 if {$r == -1} {
9062 set tags [lreplace $tags $j $j]
9063 incr j -1
9064 incr i -1
9065 } elseif {$r == 1} {
9066 set tags [lreplace $tags $i $i]
9067 incr i -1
9068 break
9073 if {[array names growing] ne {}} {
9074 # graph isn't finished, need to check if any tag could get
9075 # eclipsed by another tag coming later. Simply ignore any
9076 # tags that could later get eclipsed.
9077 set ctags {}
9078 foreach t $tags {
9079 if {[is_certain $origid $t]} {
9080 lappend ctags $t
9083 if {$tags eq $ctags} {
9084 set cached_atags($origid) $tags
9085 } else {
9086 set tags $ctags
9088 } else {
9089 set cached_atags($origid) $tags
9091 set t3 [clock clicks -milliseconds]
9092 if {0 && $t3 - $t1 >= 100} {
9093 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
9094 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9096 return $tags
9099 # Return the list of IDs that have heads that are descendents of id,
9100 # including id itself if it has a head.
9101 proc descheads {id} {
9102 global arcnos arcstart arcids archeads idheads cached_dheads
9103 global allparents
9105 if {![info exists allparents($id)]} {
9106 return {}
9108 set aret {}
9109 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9110 # part-way along an arc; check it first
9111 set a [lindex $arcnos($id) 0]
9112 if {$archeads($a) ne {}} {
9113 validate_archeads $a
9114 set i [lsearch -exact $arcids($a) $id]
9115 foreach t $archeads($a) {
9116 set j [lsearch -exact $arcids($a) $t]
9117 if {$j > $i} break
9118 lappend aret $t
9121 set id $arcstart($a)
9123 set origid $id
9124 set todo [list $id]
9125 set seen($id) 1
9126 set ret {}
9127 for {set i 0} {$i < [llength $todo]} {incr i} {
9128 set id [lindex $todo $i]
9129 if {[info exists cached_dheads($id)]} {
9130 set ret [concat $ret $cached_dheads($id)]
9131 } else {
9132 if {[info exists idheads($id)]} {
9133 lappend ret $id
9135 foreach a $arcnos($id) {
9136 if {$archeads($a) ne {}} {
9137 validate_archeads $a
9138 if {$archeads($a) ne {}} {
9139 set ret [concat $ret $archeads($a)]
9142 set d $arcstart($a)
9143 if {![info exists seen($d)]} {
9144 lappend todo $d
9145 set seen($d) 1
9150 set ret [lsort -unique $ret]
9151 set cached_dheads($origid) $ret
9152 return [concat $ret $aret]
9155 proc addedtag {id} {
9156 global arcnos arcout cached_dtags cached_atags
9158 if {![info exists arcnos($id)]} return
9159 if {![info exists arcout($id)]} {
9160 recalcarc [lindex $arcnos($id) 0]
9162 catch {unset cached_dtags}
9163 catch {unset cached_atags}
9166 proc addedhead {hid head} {
9167 global arcnos arcout cached_dheads
9169 if {![info exists arcnos($hid)]} return
9170 if {![info exists arcout($hid)]} {
9171 recalcarc [lindex $arcnos($hid) 0]
9173 catch {unset cached_dheads}
9176 proc removedhead {hid head} {
9177 global cached_dheads
9179 catch {unset cached_dheads}
9182 proc movedhead {hid head} {
9183 global arcnos arcout cached_dheads
9185 if {![info exists arcnos($hid)]} return
9186 if {![info exists arcout($hid)]} {
9187 recalcarc [lindex $arcnos($hid) 0]
9189 catch {unset cached_dheads}
9192 proc changedrefs {} {
9193 global cached_dheads cached_dtags cached_atags
9194 global arctags archeads arcnos arcout idheads idtags
9196 foreach id [concat [array names idheads] [array names idtags]] {
9197 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9198 set a [lindex $arcnos($id) 0]
9199 if {![info exists donearc($a)]} {
9200 recalcarc $a
9201 set donearc($a) 1
9205 catch {unset cached_dtags}
9206 catch {unset cached_atags}
9207 catch {unset cached_dheads}
9210 proc rereadrefs {} {
9211 global idtags idheads idotherrefs mainheadid
9213 set refids [concat [array names idtags] \
9214 [array names idheads] [array names idotherrefs]]
9215 foreach id $refids {
9216 if {![info exists ref($id)]} {
9217 set ref($id) [listrefs $id]
9220 set oldmainhead $mainheadid
9221 readrefs
9222 changedrefs
9223 set refids [lsort -unique [concat $refids [array names idtags] \
9224 [array names idheads] [array names idotherrefs]]]
9225 foreach id $refids {
9226 set v [listrefs $id]
9227 if {![info exists ref($id)] || $ref($id) != $v} {
9228 redrawtags $id
9231 if {$oldmainhead ne $mainheadid} {
9232 redrawtags $oldmainhead
9233 redrawtags $mainheadid
9235 run refill_reflist
9238 proc listrefs {id} {
9239 global idtags idheads idotherrefs
9241 set x {}
9242 if {[info exists idtags($id)]} {
9243 set x $idtags($id)
9245 set y {}
9246 if {[info exists idheads($id)]} {
9247 set y $idheads($id)
9249 set z {}
9250 if {[info exists idotherrefs($id)]} {
9251 set z $idotherrefs($id)
9253 return [list $x $y $z]
9256 proc showtag {tag isnew} {
9257 global ctext tagcontents tagids linknum tagobjid
9259 if {$isnew} {
9260 addtohistory [list showtag $tag 0]
9262 $ctext conf -state normal
9263 clear_ctext
9264 settabs 0
9265 set linknum 0
9266 if {![info exists tagcontents($tag)]} {
9267 catch {
9268 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9271 if {[info exists tagcontents($tag)]} {
9272 set text $tagcontents($tag)
9273 } else {
9274 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9276 appendwithlinks $text {}
9277 $ctext conf -state disabled
9278 init_flist {}
9281 proc doquit {} {
9282 global stopped
9283 global gitktmpdir
9285 set stopped 100
9286 savestuff .
9287 destroy .
9289 if {[info exists gitktmpdir]} {
9290 catch {file delete -force $gitktmpdir}
9294 proc mkfontdisp {font top which} {
9295 global fontattr fontpref $font
9297 set fontpref($font) [set $font]
9298 button $top.${font}but -text $which -font optionfont \
9299 -command [list choosefont $font $which]
9300 label $top.$font -relief flat -font $font \
9301 -text $fontattr($font,family) -justify left
9302 grid x $top.${font}but $top.$font -sticky w
9305 proc choosefont {font which} {
9306 global fontparam fontlist fonttop fontattr
9308 set fontparam(which) $which
9309 set fontparam(font) $font
9310 set fontparam(family) [font actual $font -family]
9311 set fontparam(size) $fontattr($font,size)
9312 set fontparam(weight) $fontattr($font,weight)
9313 set fontparam(slant) $fontattr($font,slant)
9314 set top .gitkfont
9315 set fonttop $top
9316 if {![winfo exists $top]} {
9317 font create sample
9318 eval font config sample [font actual $font]
9319 toplevel $top
9320 wm title $top [mc "Gitk font chooser"]
9321 label $top.l -textvariable fontparam(which)
9322 pack $top.l -side top
9323 set fontlist [lsort [font families]]
9324 frame $top.f
9325 listbox $top.f.fam -listvariable fontlist \
9326 -yscrollcommand [list $top.f.sb set]
9327 bind $top.f.fam <<ListboxSelect>> selfontfam
9328 scrollbar $top.f.sb -command [list $top.f.fam yview]
9329 pack $top.f.sb -side right -fill y
9330 pack $top.f.fam -side left -fill both -expand 1
9331 pack $top.f -side top -fill both -expand 1
9332 frame $top.g
9333 spinbox $top.g.size -from 4 -to 40 -width 4 \
9334 -textvariable fontparam(size) \
9335 -validatecommand {string is integer -strict %s}
9336 checkbutton $top.g.bold -padx 5 \
9337 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9338 -variable fontparam(weight) -onvalue bold -offvalue normal
9339 checkbutton $top.g.ital -padx 5 \
9340 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9341 -variable fontparam(slant) -onvalue italic -offvalue roman
9342 pack $top.g.size $top.g.bold $top.g.ital -side left
9343 pack $top.g -side top
9344 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9345 -background white
9346 $top.c create text 100 25 -anchor center -text $which -font sample \
9347 -fill black -tags text
9348 bind $top.c <Configure> [list centertext $top.c]
9349 pack $top.c -side top -fill x
9350 frame $top.buts
9351 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9352 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9353 grid $top.buts.ok $top.buts.can
9354 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9355 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9356 pack $top.buts -side bottom -fill x
9357 trace add variable fontparam write chg_fontparam
9358 } else {
9359 raise $top
9360 $top.c itemconf text -text $which
9362 set i [lsearch -exact $fontlist $fontparam(family)]
9363 if {$i >= 0} {
9364 $top.f.fam selection set $i
9365 $top.f.fam see $i
9369 proc centertext {w} {
9370 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9373 proc fontok {} {
9374 global fontparam fontpref prefstop
9376 set f $fontparam(font)
9377 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9378 if {$fontparam(weight) eq "bold"} {
9379 lappend fontpref($f) "bold"
9381 if {$fontparam(slant) eq "italic"} {
9382 lappend fontpref($f) "italic"
9384 set w $prefstop.$f
9385 $w conf -text $fontparam(family) -font $fontpref($f)
9387 fontcan
9390 proc fontcan {} {
9391 global fonttop fontparam
9393 if {[info exists fonttop]} {
9394 catch {destroy $fonttop}
9395 catch {font delete sample}
9396 unset fonttop
9397 unset fontparam
9401 proc selfontfam {} {
9402 global fonttop fontparam
9404 set i [$fonttop.f.fam curselection]
9405 if {$i ne {}} {
9406 set fontparam(family) [$fonttop.f.fam get $i]
9410 proc chg_fontparam {v sub op} {
9411 global fontparam
9413 font config sample -$sub $fontparam($sub)
9416 proc doprefs {} {
9417 global maxwidth maxgraphpct
9418 global oldprefs prefstop showneartags showlocalchanges
9419 global bgcolor fgcolor ctext diffcolors selectbgcolor
9420 global tabstop limitdiffs autoselect extdifftool perfile_attrs
9422 set top .gitkprefs
9423 set prefstop $top
9424 if {[winfo exists $top]} {
9425 raise $top
9426 return
9428 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9429 limitdiffs tabstop perfile_attrs} {
9430 set oldprefs($v) [set $v]
9432 toplevel $top
9433 wm title $top [mc "Gitk preferences"]
9434 label $top.ldisp -text [mc "Commit list display options"]
9435 grid $top.ldisp - -sticky w -pady 10
9436 label $top.spacer -text " "
9437 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9438 -font optionfont
9439 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9440 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9441 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9442 -font optionfont
9443 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9444 grid x $top.maxpctl $top.maxpct -sticky w
9445 frame $top.showlocal
9446 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9447 checkbutton $top.showlocal.b -variable showlocalchanges
9448 pack $top.showlocal.b $top.showlocal.l -side left
9449 grid x $top.showlocal -sticky w
9450 frame $top.autoselect
9451 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9452 checkbutton $top.autoselect.b -variable autoselect
9453 pack $top.autoselect.b $top.autoselect.l -side left
9454 grid x $top.autoselect -sticky w
9456 label $top.ddisp -text [mc "Diff display options"]
9457 grid $top.ddisp - -sticky w -pady 10
9458 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9459 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9460 grid x $top.tabstopl $top.tabstop -sticky w
9461 frame $top.ntag
9462 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9463 checkbutton $top.ntag.b -variable showneartags
9464 pack $top.ntag.b $top.ntag.l -side left
9465 grid x $top.ntag -sticky w
9466 frame $top.ldiff
9467 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9468 checkbutton $top.ldiff.b -variable limitdiffs
9469 pack $top.ldiff.b $top.ldiff.l -side left
9470 grid x $top.ldiff -sticky w
9471 frame $top.lattr
9472 label $top.lattr.l -text [mc "Support per-file encodings"] -font optionfont
9473 checkbutton $top.lattr.b -variable perfile_attrs
9474 pack $top.lattr.b $top.lattr.l -side left
9475 grid x $top.lattr -sticky w
9477 entry $top.extdifft -textvariable extdifftool
9478 frame $top.extdifff
9479 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
9480 -padx 10
9481 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
9482 -command choose_extdiff
9483 pack $top.extdifff.l $top.extdifff.b -side left
9484 grid x $top.extdifff $top.extdifft -sticky w
9486 label $top.cdisp -text [mc "Colors: press to choose"]
9487 grid $top.cdisp - -sticky w -pady 10
9488 label $top.bg -padx 40 -relief sunk -background $bgcolor
9489 button $top.bgbut -text [mc "Background"] -font optionfont \
9490 -command [list choosecolor bgcolor {} $top.bg background setbg]
9491 grid x $top.bgbut $top.bg -sticky w
9492 label $top.fg -padx 40 -relief sunk -background $fgcolor
9493 button $top.fgbut -text [mc "Foreground"] -font optionfont \
9494 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
9495 grid x $top.fgbut $top.fg -sticky w
9496 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
9497 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
9498 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
9499 [list $ctext tag conf d0 -foreground]]
9500 grid x $top.diffoldbut $top.diffold -sticky w
9501 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
9502 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
9503 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
9504 [list $ctext tag conf d1 -foreground]]
9505 grid x $top.diffnewbut $top.diffnew -sticky w
9506 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
9507 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
9508 -command [list choosecolor diffcolors 2 $top.hunksep \
9509 "diff hunk header" \
9510 [list $ctext tag conf hunksep -foreground]]
9511 grid x $top.hunksepbut $top.hunksep -sticky w
9512 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
9513 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
9514 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
9515 grid x $top.selbgbut $top.selbgsep -sticky w
9517 label $top.cfont -text [mc "Fonts: press to choose"]
9518 grid $top.cfont - -sticky w -pady 10
9519 mkfontdisp mainfont $top [mc "Main font"]
9520 mkfontdisp textfont $top [mc "Diff display font"]
9521 mkfontdisp uifont $top [mc "User interface font"]
9523 frame $top.buts
9524 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
9525 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
9526 grid $top.buts.ok $top.buts.can
9527 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9528 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9529 grid $top.buts - - -pady 10 -sticky ew
9530 bind $top <Visibility> "focus $top.buts.ok"
9533 proc choose_extdiff {} {
9534 global extdifftool
9536 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
9537 if {$prog ne {}} {
9538 set extdifftool $prog
9542 proc choosecolor {v vi w x cmd} {
9543 global $v
9545 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
9546 -title [mc "Gitk: choose color for %s" $x]]
9547 if {$c eq {}} return
9548 $w conf -background $c
9549 lset $v $vi $c
9550 eval $cmd $c
9553 proc setselbg {c} {
9554 global bglist cflist
9555 foreach w $bglist {
9556 $w configure -selectbackground $c
9558 $cflist tag configure highlight \
9559 -background [$cflist cget -selectbackground]
9560 allcanvs itemconf secsel -fill $c
9563 proc setbg {c} {
9564 global bglist
9566 foreach w $bglist {
9567 $w conf -background $c
9571 proc setfg {c} {
9572 global fglist canv
9574 foreach w $fglist {
9575 $w conf -foreground $c
9577 allcanvs itemconf text -fill $c
9578 $canv itemconf circle -outline $c
9581 proc prefscan {} {
9582 global oldprefs prefstop
9584 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9585 limitdiffs tabstop perfile_attrs} {
9586 global $v
9587 set $v $oldprefs($v)
9589 catch {destroy $prefstop}
9590 unset prefstop
9591 fontcan
9594 proc prefsok {} {
9595 global maxwidth maxgraphpct
9596 global oldprefs prefstop showneartags showlocalchanges
9597 global fontpref mainfont textfont uifont
9598 global limitdiffs treediffs perfile_attrs
9600 catch {destroy $prefstop}
9601 unset prefstop
9602 fontcan
9603 set fontchanged 0
9604 if {$mainfont ne $fontpref(mainfont)} {
9605 set mainfont $fontpref(mainfont)
9606 parsefont mainfont $mainfont
9607 eval font configure mainfont [fontflags mainfont]
9608 eval font configure mainfontbold [fontflags mainfont 1]
9609 setcoords
9610 set fontchanged 1
9612 if {$textfont ne $fontpref(textfont)} {
9613 set textfont $fontpref(textfont)
9614 parsefont textfont $textfont
9615 eval font configure textfont [fontflags textfont]
9616 eval font configure textfontbold [fontflags textfont 1]
9618 if {$uifont ne $fontpref(uifont)} {
9619 set uifont $fontpref(uifont)
9620 parsefont uifont $uifont
9621 eval font configure uifont [fontflags uifont]
9623 settabs
9624 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
9625 if {$showlocalchanges} {
9626 doshowlocalchanges
9627 } else {
9628 dohidelocalchanges
9631 if {$limitdiffs != $oldprefs(limitdiffs) ||
9632 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
9633 # treediffs elements are limited by path;
9634 # won't have encodings cached if perfile_attrs was just turned on
9635 catch {unset treediffs}
9637 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
9638 || $maxgraphpct != $oldprefs(maxgraphpct)} {
9639 redisplay
9640 } elseif {$showneartags != $oldprefs(showneartags) ||
9641 $limitdiffs != $oldprefs(limitdiffs)} {
9642 reselectline
9646 proc formatdate {d} {
9647 global datetimeformat
9648 if {$d ne {}} {
9649 set d [clock format $d -format $datetimeformat]
9651 return $d
9654 # This list of encoding names and aliases is distilled from
9655 # http://www.iana.org/assignments/character-sets.
9656 # Not all of them are supported by Tcl.
9657 set encoding_aliases {
9658 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
9659 ISO646-US US-ASCII us IBM367 cp367 csASCII }
9660 { ISO-10646-UTF-1 csISO10646UTF1 }
9661 { ISO_646.basic:1983 ref csISO646basic1983 }
9662 { INVARIANT csINVARIANT }
9663 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
9664 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
9665 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
9666 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
9667 { NATS-DANO iso-ir-9-1 csNATSDANO }
9668 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
9669 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
9670 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
9671 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
9672 { ISO-2022-KR csISO2022KR }
9673 { EUC-KR csEUCKR }
9674 { ISO-2022-JP csISO2022JP }
9675 { ISO-2022-JP-2 csISO2022JP2 }
9676 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
9677 csISO13JISC6220jp }
9678 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
9679 { IT iso-ir-15 ISO646-IT csISO15Italian }
9680 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
9681 { ES iso-ir-17 ISO646-ES csISO17Spanish }
9682 { greek7-old iso-ir-18 csISO18Greek7Old }
9683 { latin-greek iso-ir-19 csISO19LatinGreek }
9684 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
9685 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
9686 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
9687 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
9688 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
9689 { BS_viewdata iso-ir-47 csISO47BSViewdata }
9690 { INIS iso-ir-49 csISO49INIS }
9691 { INIS-8 iso-ir-50 csISO50INIS8 }
9692 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
9693 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
9694 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
9695 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
9696 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
9697 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
9698 csISO60Norwegian1 }
9699 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
9700 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
9701 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
9702 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
9703 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
9704 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
9705 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
9706 { greek7 iso-ir-88 csISO88Greek7 }
9707 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
9708 { iso-ir-90 csISO90 }
9709 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
9710 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
9711 csISO92JISC62991984b }
9712 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
9713 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
9714 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
9715 csISO95JIS62291984handadd }
9716 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
9717 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
9718 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
9719 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
9720 CP819 csISOLatin1 }
9721 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
9722 { T.61-7bit iso-ir-102 csISO102T617bit }
9723 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
9724 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
9725 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
9726 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
9727 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
9728 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
9729 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
9730 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
9731 arabic csISOLatinArabic }
9732 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
9733 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
9734 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
9735 greek greek8 csISOLatinGreek }
9736 { T.101-G2 iso-ir-128 csISO128T101G2 }
9737 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
9738 csISOLatinHebrew }
9739 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
9740 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
9741 { CSN_369103 iso-ir-139 csISO139CSN369103 }
9742 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
9743 { ISO_6937-2-add iso-ir-142 csISOTextComm }
9744 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
9745 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
9746 csISOLatinCyrillic }
9747 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
9748 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
9749 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
9750 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
9751 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
9752 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
9753 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
9754 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
9755 { ISO_10367-box iso-ir-155 csISO10367Box }
9756 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
9757 { latin-lap lap iso-ir-158 csISO158Lap }
9758 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
9759 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
9760 { us-dk csUSDK }
9761 { dk-us csDKUS }
9762 { JIS_X0201 X0201 csHalfWidthKatakana }
9763 { KSC5636 ISO646-KR csKSC5636 }
9764 { ISO-10646-UCS-2 csUnicode }
9765 { ISO-10646-UCS-4 csUCS4 }
9766 { DEC-MCS dec csDECMCS }
9767 { hp-roman8 roman8 r8 csHPRoman8 }
9768 { macintosh mac csMacintosh }
9769 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
9770 csIBM037 }
9771 { IBM038 EBCDIC-INT cp038 csIBM038 }
9772 { IBM273 CP273 csIBM273 }
9773 { IBM274 EBCDIC-BE CP274 csIBM274 }
9774 { IBM275 EBCDIC-BR cp275 csIBM275 }
9775 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
9776 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
9777 { IBM280 CP280 ebcdic-cp-it csIBM280 }
9778 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
9779 { IBM284 CP284 ebcdic-cp-es csIBM284 }
9780 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
9781 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
9782 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
9783 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
9784 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
9785 { IBM424 cp424 ebcdic-cp-he csIBM424 }
9786 { IBM437 cp437 437 csPC8CodePage437 }
9787 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
9788 { IBM775 cp775 csPC775Baltic }
9789 { IBM850 cp850 850 csPC850Multilingual }
9790 { IBM851 cp851 851 csIBM851 }
9791 { IBM852 cp852 852 csPCp852 }
9792 { IBM855 cp855 855 csIBM855 }
9793 { IBM857 cp857 857 csIBM857 }
9794 { IBM860 cp860 860 csIBM860 }
9795 { IBM861 cp861 861 cp-is csIBM861 }
9796 { IBM862 cp862 862 csPC862LatinHebrew }
9797 { IBM863 cp863 863 csIBM863 }
9798 { IBM864 cp864 csIBM864 }
9799 { IBM865 cp865 865 csIBM865 }
9800 { IBM866 cp866 866 csIBM866 }
9801 { IBM868 CP868 cp-ar csIBM868 }
9802 { IBM869 cp869 869 cp-gr csIBM869 }
9803 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
9804 { IBM871 CP871 ebcdic-cp-is csIBM871 }
9805 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
9806 { IBM891 cp891 csIBM891 }
9807 { IBM903 cp903 csIBM903 }
9808 { IBM904 cp904 904 csIBBM904 }
9809 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
9810 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
9811 { IBM1026 CP1026 csIBM1026 }
9812 { EBCDIC-AT-DE csIBMEBCDICATDE }
9813 { EBCDIC-AT-DE-A csEBCDICATDEA }
9814 { EBCDIC-CA-FR csEBCDICCAFR }
9815 { EBCDIC-DK-NO csEBCDICDKNO }
9816 { EBCDIC-DK-NO-A csEBCDICDKNOA }
9817 { EBCDIC-FI-SE csEBCDICFISE }
9818 { EBCDIC-FI-SE-A csEBCDICFISEA }
9819 { EBCDIC-FR csEBCDICFR }
9820 { EBCDIC-IT csEBCDICIT }
9821 { EBCDIC-PT csEBCDICPT }
9822 { EBCDIC-ES csEBCDICES }
9823 { EBCDIC-ES-A csEBCDICESA }
9824 { EBCDIC-ES-S csEBCDICESS }
9825 { EBCDIC-UK csEBCDICUK }
9826 { EBCDIC-US csEBCDICUS }
9827 { UNKNOWN-8BIT csUnknown8BiT }
9828 { MNEMONIC csMnemonic }
9829 { MNEM csMnem }
9830 { VISCII csVISCII }
9831 { VIQR csVIQR }
9832 { KOI8-R csKOI8R }
9833 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
9834 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
9835 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
9836 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
9837 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
9838 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
9839 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
9840 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
9841 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
9842 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
9843 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
9844 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
9845 { IBM1047 IBM-1047 }
9846 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
9847 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
9848 { UNICODE-1-1 csUnicode11 }
9849 { CESU-8 csCESU-8 }
9850 { BOCU-1 csBOCU-1 }
9851 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
9852 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
9853 l8 }
9854 { ISO-8859-15 ISO_8859-15 Latin-9 }
9855 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
9856 { GBK CP936 MS936 windows-936 }
9857 { JIS_Encoding csJISEncoding }
9858 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
9859 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
9860 EUC-JP }
9861 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
9862 { ISO-10646-UCS-Basic csUnicodeASCII }
9863 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
9864 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
9865 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
9866 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
9867 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
9868 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
9869 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
9870 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
9871 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
9872 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
9873 { Adobe-Standard-Encoding csAdobeStandardEncoding }
9874 { Ventura-US csVenturaUS }
9875 { Ventura-International csVenturaInternational }
9876 { PC8-Danish-Norwegian csPC8DanishNorwegian }
9877 { PC8-Turkish csPC8Turkish }
9878 { IBM-Symbols csIBMSymbols }
9879 { IBM-Thai csIBMThai }
9880 { HP-Legal csHPLegal }
9881 { HP-Pi-font csHPPiFont }
9882 { HP-Math8 csHPMath8 }
9883 { Adobe-Symbol-Encoding csHPPSMath }
9884 { HP-DeskTop csHPDesktop }
9885 { Ventura-Math csVenturaMath }
9886 { Microsoft-Publishing csMicrosoftPublishing }
9887 { Windows-31J csWindows31J }
9888 { GB2312 csGB2312 }
9889 { Big5 csBig5 }
9892 proc tcl_encoding {enc} {
9893 global encoding_aliases tcl_encoding_cache
9894 if {[info exists tcl_encoding_cache($enc)]} {
9895 return $tcl_encoding_cache($enc)
9897 set names [encoding names]
9898 set lcnames [string tolower $names]
9899 set enc [string tolower $enc]
9900 set i [lsearch -exact $lcnames $enc]
9901 if {$i < 0} {
9902 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
9903 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
9904 set i [lsearch -exact $lcnames $encx]
9907 if {$i < 0} {
9908 foreach l $encoding_aliases {
9909 set ll [string tolower $l]
9910 if {[lsearch -exact $ll $enc] < 0} continue
9911 # look through the aliases for one that tcl knows about
9912 foreach e $ll {
9913 set i [lsearch -exact $lcnames $e]
9914 if {$i < 0} {
9915 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
9916 set i [lsearch -exact $lcnames $ex]
9919 if {$i >= 0} break
9921 break
9924 set tclenc {}
9925 if {$i >= 0} {
9926 set tclenc [lindex $names $i]
9928 set tcl_encoding_cache($enc) $tclenc
9929 return $tclenc
9932 proc gitattr {path attr default} {
9933 global path_attr_cache
9934 if {[info exists path_attr_cache($attr,$path)]} {
9935 set r $path_attr_cache($attr,$path)
9936 } else {
9937 set r "unspecified"
9938 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
9939 regexp "(.*): encoding: (.*)" $line m f r
9941 set path_attr_cache($attr,$path) $r
9943 if {$r eq "unspecified"} {
9944 return $default
9946 return $r
9949 proc cache_gitattr {attr pathlist} {
9950 global path_attr_cache
9951 set newlist {}
9952 foreach path $pathlist {
9953 if {![info exists path_attr_cache($attr,$path)]} {
9954 lappend newlist $path
9957 set lim 1000
9958 if {[tk windowingsystem] == "win32"} {
9959 # windows has a 32k limit on the arguments to a command...
9960 set lim 30
9962 while {$newlist ne {}} {
9963 set head [lrange $newlist 0 [expr {$lim - 1}]]
9964 set newlist [lrange $newlist $lim end]
9965 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
9966 foreach row [split $rlist "\n"] {
9967 if {[regexp "(.*): encoding: (.*)" $row m path value]} {
9968 if {[string index $path 0] eq "\""} {
9969 set path [encoding convertfrom [lindex $path 0]]
9971 set path_attr_cache($attr,$path) $value
9978 proc get_path_encoding {path} {
9979 global gui_encoding perfile_attrs
9980 set tcl_enc $gui_encoding
9981 if {$path ne {} && $perfile_attrs} {
9982 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
9983 if {$enc2 ne {}} {
9984 set tcl_enc $enc2
9987 return $tcl_enc
9990 # First check that Tcl/Tk is recent enough
9991 if {[catch {package require Tk 8.4} err]} {
9992 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
9993 Gitk requires at least Tcl/Tk 8.4."]
9994 exit 1
9997 # defaults...
9998 set wrcomcmd "git diff-tree --stdin -p --pretty"
10000 set gitencoding {}
10001 catch {
10002 set gitencoding [exec git config --get i18n.commitencoding]
10004 if {$gitencoding == ""} {
10005 set gitencoding "utf-8"
10007 set tclencoding [tcl_encoding $gitencoding]
10008 if {$tclencoding == {}} {
10009 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
10012 set gui_encoding [encoding system]
10013 catch {
10014 set enc [exec git config --get gui.encoding]
10015 if {$enc ne {}} {
10016 set tclenc [tcl_encoding $enc]
10017 if {$tclenc ne {}} {
10018 set gui_encoding $tclenc
10019 } else {
10020 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
10025 set mainfont {Helvetica 9}
10026 set textfont {Courier 9}
10027 set uifont {Helvetica 9 bold}
10028 set tabstop 8
10029 set findmergefiles 0
10030 set maxgraphpct 50
10031 set maxwidth 16
10032 set revlistorder 0
10033 set fastdate 0
10034 set uparrowlen 5
10035 set downarrowlen 5
10036 set mingaplen 100
10037 set cmitmode "patch"
10038 set wrapcomment "none"
10039 set showneartags 1
10040 set maxrefs 20
10041 set maxlinelen 200
10042 set showlocalchanges 1
10043 set limitdiffs 1
10044 set datetimeformat "%Y-%m-%d %H:%M:%S"
10045 set autoselect 1
10046 set perfile_attrs 0
10048 set extdifftool "meld"
10050 set colors {green red blue magenta darkgrey brown orange}
10051 set bgcolor white
10052 set fgcolor black
10053 set diffcolors {red "#00a000" blue}
10054 set diffcontext 3
10055 set ignorespace 0
10056 set selectbgcolor gray85
10058 set circlecolors {white blue gray blue blue}
10060 # button for popping up context menus
10061 if {[tk windowingsystem] eq "aqua"} {
10062 set ctxbut <Button-2>
10063 } else {
10064 set ctxbut <Button-3>
10067 ## For msgcat loading, first locate the installation location.
10068 if { [info exists ::env(GITK_MSGSDIR)] } {
10069 ## Msgsdir was manually set in the environment.
10070 set gitk_msgsdir $::env(GITK_MSGSDIR)
10071 } else {
10072 ## Let's guess the prefix from argv0.
10073 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
10074 set gitk_libdir [file join $gitk_prefix share gitk lib]
10075 set gitk_msgsdir [file join $gitk_libdir msgs]
10076 unset gitk_prefix
10079 ## Internationalization (i18n) through msgcat and gettext. See
10080 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
10081 package require msgcat
10082 namespace import ::msgcat::mc
10083 ## And eventually load the actual message catalog
10084 ::msgcat::mcload $gitk_msgsdir
10086 catch {source ~/.gitk}
10088 font create optionfont -family sans-serif -size -12
10090 parsefont mainfont $mainfont
10091 eval font create mainfont [fontflags mainfont]
10092 eval font create mainfontbold [fontflags mainfont 1]
10094 parsefont textfont $textfont
10095 eval font create textfont [fontflags textfont]
10096 eval font create textfontbold [fontflags textfont 1]
10098 parsefont uifont $uifont
10099 eval font create uifont [fontflags uifont]
10101 setoptions
10103 # check that we can find a .git directory somewhere...
10104 if {[catch {set gitdir [gitdir]}]} {
10105 show_error {} . [mc "Cannot find a git repository here."]
10106 exit 1
10108 if {![file isdirectory $gitdir]} {
10109 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
10110 exit 1
10113 set selecthead {}
10114 set selectheadid {}
10116 set revtreeargs {}
10117 set cmdline_files {}
10118 set i 0
10119 set revtreeargscmd {}
10120 foreach arg $argv {
10121 switch -glob -- $arg {
10122 "" { }
10123 "--" {
10124 set cmdline_files [lrange $argv [expr {$i + 1}] end]
10125 break
10127 "--select-commit=*" {
10128 set selecthead [string range $arg 16 end]
10130 "--argscmd=*" {
10131 set revtreeargscmd [string range $arg 10 end]
10133 default {
10134 lappend revtreeargs $arg
10137 incr i
10140 if {$selecthead eq "HEAD"} {
10141 set selecthead {}
10144 if {$i >= [llength $argv] && $revtreeargs ne {}} {
10145 # no -- on command line, but some arguments (other than --argscmd)
10146 if {[catch {
10147 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
10148 set cmdline_files [split $f "\n"]
10149 set n [llength $cmdline_files]
10150 set revtreeargs [lrange $revtreeargs 0 end-$n]
10151 # Unfortunately git rev-parse doesn't produce an error when
10152 # something is both a revision and a filename. To be consistent
10153 # with git log and git rev-list, check revtreeargs for filenames.
10154 foreach arg $revtreeargs {
10155 if {[file exists $arg]} {
10156 show_error {} . [mc "Ambiguous argument '%s': both revision\
10157 and filename" $arg]
10158 exit 1
10161 } err]} {
10162 # unfortunately we get both stdout and stderr in $err,
10163 # so look for "fatal:".
10164 set i [string first "fatal:" $err]
10165 if {$i > 0} {
10166 set err [string range $err [expr {$i + 6}] end]
10168 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
10169 exit 1
10173 set nullid "0000000000000000000000000000000000000000"
10174 set nullid2 "0000000000000000000000000000000000000001"
10175 set nullfile "/dev/null"
10177 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
10179 set runq {}
10180 set history {}
10181 set historyindex 0
10182 set fh_serial 0
10183 set nhl_names {}
10184 set highlight_paths {}
10185 set findpattern {}
10186 set searchdirn -forwards
10187 set boldrows {}
10188 set boldnamerows {}
10189 set diffelide {0 0}
10190 set markingmatches 0
10191 set linkentercount 0
10192 set need_redisplay 0
10193 set nrows_drawn 0
10194 set firsttabstop 0
10196 set nextviewnum 1
10197 set curview 0
10198 set selectedview 0
10199 set selectedhlview [mc "None"]
10200 set highlight_related [mc "None"]
10201 set highlight_files {}
10202 set viewfiles(0) {}
10203 set viewperm(0) 0
10204 set viewargs(0) {}
10205 set viewargscmd(0) {}
10207 set selectedline {}
10208 set numcommits 0
10209 set loginstance 0
10210 set cmdlineok 0
10211 set stopped 0
10212 set stuffsaved 0
10213 set patchnum 0
10214 set lserial 0
10215 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
10216 setcoords
10217 makewindow
10218 # wait for the window to become visible
10219 tkwait visibility .
10220 wm title . "[file tail $argv0]: [file tail [pwd]]"
10221 readrefs
10223 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
10224 # create a view for the files/dirs specified on the command line
10225 set curview 1
10226 set selectedview 1
10227 set nextviewnum 2
10228 set viewname(1) [mc "Command line"]
10229 set viewfiles(1) $cmdline_files
10230 set viewargs(1) $revtreeargs
10231 set viewargscmd(1) $revtreeargscmd
10232 set viewperm(1) 0
10233 set vdatemode(1) 0
10234 addviewmenu 1
10235 .bar.view entryconf [mca "Edit view..."] -state normal
10236 .bar.view entryconf [mca "Delete view"] -state normal
10239 if {[info exists permviews]} {
10240 foreach v $permviews {
10241 set n $nextviewnum
10242 incr nextviewnum
10243 set viewname($n) [lindex $v 0]
10244 set viewfiles($n) [lindex $v 1]
10245 set viewargs($n) [lindex $v 2]
10246 set viewargscmd($n) [lindex $v 3]
10247 set viewperm($n) 1
10248 addviewmenu $n
10251 getcommits {}