gitk: Fix transient windows on Win32 and MacOS
[git/dscho.git] / gitk
blobe6aafe8a687ee501a86666a00b2ccd9d5a014114
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 make_transient {window origin} {
1743 global have_tk85
1745 # In MacOS Tk 8.4 transient appears to work by setting
1746 # overrideredirect, which is utterly useless, since the
1747 # windows get no border, and are not even kept above
1748 # the parent.
1749 if {!$have_tk85 && [tk windowingsystem] eq {aqua}} return
1751 wm transient $window $origin
1753 # Windows fails to place transient windows normally, so
1754 # schedule a callback to center them on the parent.
1755 if {[tk windowingsystem] eq {win32}} {
1756 after idle [list tk::PlaceWindow $window widget $origin]
1760 proc show_error {w top msg} {
1761 message $w.m -text $msg -justify center -aspect 400
1762 pack $w.m -side top -fill x -padx 20 -pady 20
1763 button $w.ok -text [mc OK] -command "destroy $top"
1764 pack $w.ok -side bottom -fill x
1765 bind $top <Visibility> "grab $top; focus $top"
1766 bind $top <Key-Return> "destroy $top"
1767 bind $top <Key-space> "destroy $top"
1768 bind $top <Key-Escape> "destroy $top"
1769 tkwait window $top
1772 proc error_popup {msg {owner .}} {
1773 set w .error
1774 toplevel $w
1775 make_transient $w $owner
1776 show_error $w $w $msg
1779 proc confirm_popup {msg {owner .}} {
1780 global confirm_ok
1781 set confirm_ok 0
1782 set w .confirm
1783 toplevel $w
1784 make_transient $w $owner
1785 message $w.m -text $msg -justify center -aspect 400
1786 pack $w.m -side top -fill x -padx 20 -pady 20
1787 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1788 pack $w.ok -side left -fill x
1789 button $w.cancel -text [mc Cancel] -command "destroy $w"
1790 pack $w.cancel -side right -fill x
1791 bind $w <Visibility> "grab $w; focus $w"
1792 bind $w <Key-Return> "set confirm_ok 1; destroy $w"
1793 bind $w <Key-space> "set confirm_ok 1; destroy $w"
1794 bind $w <Key-Escape> "destroy $w"
1795 tkwait window $w
1796 return $confirm_ok
1799 proc setoptions {} {
1800 option add *Panedwindow.showHandle 1 startupFile
1801 option add *Panedwindow.sashRelief raised startupFile
1802 option add *Button.font uifont startupFile
1803 option add *Checkbutton.font uifont startupFile
1804 option add *Radiobutton.font uifont startupFile
1805 option add *Menu.font uifont startupFile
1806 option add *Menubutton.font uifont startupFile
1807 option add *Label.font uifont startupFile
1808 option add *Message.font uifont startupFile
1809 option add *Entry.font uifont startupFile
1812 # Make a menu and submenus.
1813 # m is the window name for the menu, items is the list of menu items to add.
1814 # Each item is a list {mc label type description options...}
1815 # mc is ignored; it's so we can put mc there to alert xgettext
1816 # label is the string that appears in the menu
1817 # type is cascade, command or radiobutton (should add checkbutton)
1818 # description depends on type; it's the sublist for cascade, the
1819 # command to invoke for command, or {variable value} for radiobutton
1820 proc makemenu {m items} {
1821 menu $m
1822 if {[tk windowingsystem] eq {aqua}} {
1823 set Meta1 Cmd
1824 } else {
1825 set Meta1 Ctrl
1827 foreach i $items {
1828 set name [mc [lindex $i 1]]
1829 set type [lindex $i 2]
1830 set thing [lindex $i 3]
1831 set params [list $type]
1832 if {$name ne {}} {
1833 set u [string first "&" [string map {&& x} $name]]
1834 lappend params -label [string map {&& & & {}} $name]
1835 if {$u >= 0} {
1836 lappend params -underline $u
1839 switch -- $type {
1840 "cascade" {
1841 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1842 lappend params -menu $m.$submenu
1844 "command" {
1845 lappend params -command $thing
1847 "radiobutton" {
1848 lappend params -variable [lindex $thing 0] \
1849 -value [lindex $thing 1]
1852 set tail [lrange $i 4 end]
1853 regsub -all {\yMeta1\y} $tail $Meta1 tail
1854 eval $m add $params $tail
1855 if {$type eq "cascade"} {
1856 makemenu $m.$submenu $thing
1861 # translate string and remove ampersands
1862 proc mca {str} {
1863 return [string map {&& & & {}} [mc $str]]
1866 proc makewindow {} {
1867 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1868 global tabstop
1869 global findtype findtypemenu findloc findstring fstring geometry
1870 global entries sha1entry sha1string sha1but
1871 global diffcontextstring diffcontext
1872 global ignorespace
1873 global maincursor textcursor curtextcursor
1874 global rowctxmenu fakerowmenu mergemax wrapcomment
1875 global highlight_files gdttype
1876 global searchstring sstring
1877 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1878 global headctxmenu progresscanv progressitem progresscoords statusw
1879 global fprogitem fprogcoord lastprogupdate progupdatepending
1880 global rprogitem rprogcoord rownumsel numcommits
1881 global have_tk85
1883 # The "mc" arguments here are purely so that xgettext
1884 # sees the following string as needing to be translated
1885 makemenu .bar {
1886 {mc "File" cascade {
1887 {mc "Update" command updatecommits -accelerator F5}
1888 {mc "Reload" command reloadcommits -accelerator Meta1-F5}
1889 {mc "Reread references" command rereadrefs}
1890 {mc "List references" command showrefs -accelerator F2}
1891 {mc "Quit" command doquit -accelerator Meta1-Q}
1893 {mc "Edit" cascade {
1894 {mc "Preferences" command doprefs}
1896 {mc "View" cascade {
1897 {mc "New view..." command {newview 0} -accelerator Shift-F4}
1898 {mc "Edit view..." command editview -state disabled -accelerator F4}
1899 {mc "Delete view" command delview -state disabled}
1900 {xx "" separator}
1901 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
1903 {mc "Help" cascade {
1904 {mc "About gitk" command about}
1905 {mc "Key bindings" command keys}
1908 . configure -menu .bar
1910 # the gui has upper and lower half, parts of a paned window.
1911 panedwindow .ctop -orient vertical
1913 # possibly use assumed geometry
1914 if {![info exists geometry(pwsash0)]} {
1915 set geometry(topheight) [expr {15 * $linespc}]
1916 set geometry(topwidth) [expr {80 * $charspc}]
1917 set geometry(botheight) [expr {15 * $linespc}]
1918 set geometry(botwidth) [expr {50 * $charspc}]
1919 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1920 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1923 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1924 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1925 frame .tf.histframe
1926 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1928 # create three canvases
1929 set cscroll .tf.histframe.csb
1930 set canv .tf.histframe.pwclist.canv
1931 canvas $canv \
1932 -selectbackground $selectbgcolor \
1933 -background $bgcolor -bd 0 \
1934 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1935 .tf.histframe.pwclist add $canv
1936 set canv2 .tf.histframe.pwclist.canv2
1937 canvas $canv2 \
1938 -selectbackground $selectbgcolor \
1939 -background $bgcolor -bd 0 -yscrollincr $linespc
1940 .tf.histframe.pwclist add $canv2
1941 set canv3 .tf.histframe.pwclist.canv3
1942 canvas $canv3 \
1943 -selectbackground $selectbgcolor \
1944 -background $bgcolor -bd 0 -yscrollincr $linespc
1945 .tf.histframe.pwclist add $canv3
1946 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1947 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1949 # a scroll bar to rule them
1950 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1951 pack $cscroll -side right -fill y
1952 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1953 lappend bglist $canv $canv2 $canv3
1954 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1956 # we have two button bars at bottom of top frame. Bar 1
1957 frame .tf.bar
1958 frame .tf.lbar -height 15
1960 set sha1entry .tf.bar.sha1
1961 set entries $sha1entry
1962 set sha1but .tf.bar.sha1label
1963 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1964 -command gotocommit -width 8
1965 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1966 pack .tf.bar.sha1label -side left
1967 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1968 trace add variable sha1string write sha1change
1969 pack $sha1entry -side left -pady 2
1971 image create bitmap bm-left -data {
1972 #define left_width 16
1973 #define left_height 16
1974 static unsigned char left_bits[] = {
1975 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1976 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1977 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1979 image create bitmap bm-right -data {
1980 #define right_width 16
1981 #define right_height 16
1982 static unsigned char right_bits[] = {
1983 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1984 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1985 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1987 button .tf.bar.leftbut -image bm-left -command goback \
1988 -state disabled -width 26
1989 pack .tf.bar.leftbut -side left -fill y
1990 button .tf.bar.rightbut -image bm-right -command goforw \
1991 -state disabled -width 26
1992 pack .tf.bar.rightbut -side left -fill y
1994 label .tf.bar.rowlabel -text [mc "Row"]
1995 set rownumsel {}
1996 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
1997 -relief sunken -anchor e
1998 label .tf.bar.rowlabel2 -text "/"
1999 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
2000 -relief sunken -anchor e
2001 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
2002 -side left
2003 global selectedline
2004 trace add variable selectedline write selectedline_change
2006 # Status label and progress bar
2007 set statusw .tf.bar.status
2008 label $statusw -width 15 -relief sunken
2009 pack $statusw -side left -padx 5
2010 set h [expr {[font metrics uifont -linespace] + 2}]
2011 set progresscanv .tf.bar.progress
2012 canvas $progresscanv -relief sunken -height $h -borderwidth 2
2013 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
2014 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
2015 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
2016 pack $progresscanv -side right -expand 1 -fill x
2017 set progresscoords {0 0}
2018 set fprogcoord 0
2019 set rprogcoord 0
2020 bind $progresscanv <Configure> adjustprogress
2021 set lastprogupdate [clock clicks -milliseconds]
2022 set progupdatepending 0
2024 # build up the bottom bar of upper window
2025 label .tf.lbar.flabel -text "[mc "Find"] "
2026 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
2027 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
2028 label .tf.lbar.flab2 -text " [mc "commit"] "
2029 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2030 -side left -fill y
2031 set gdttype [mc "containing:"]
2032 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
2033 [mc "containing:"] \
2034 [mc "touching paths:"] \
2035 [mc "adding/removing string:"]]
2036 trace add variable gdttype write gdttype_change
2037 pack .tf.lbar.gdttype -side left -fill y
2039 set findstring {}
2040 set fstring .tf.lbar.findstring
2041 lappend entries $fstring
2042 entry $fstring -width 30 -font textfont -textvariable findstring
2043 trace add variable findstring write find_change
2044 set findtype [mc "Exact"]
2045 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
2046 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2047 trace add variable findtype write findcom_change
2048 set findloc [mc "All fields"]
2049 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2050 [mc "Comments"] [mc "Author"] [mc "Committer"]
2051 trace add variable findloc write find_change
2052 pack .tf.lbar.findloc -side right
2053 pack .tf.lbar.findtype -side right
2054 pack $fstring -side left -expand 1 -fill x
2056 # Finish putting the upper half of the viewer together
2057 pack .tf.lbar -in .tf -side bottom -fill x
2058 pack .tf.bar -in .tf -side bottom -fill x
2059 pack .tf.histframe -fill both -side top -expand 1
2060 .ctop add .tf
2061 .ctop paneconfigure .tf -height $geometry(topheight)
2062 .ctop paneconfigure .tf -width $geometry(topwidth)
2064 # now build up the bottom
2065 panedwindow .pwbottom -orient horizontal
2067 # lower left, a text box over search bar, scroll bar to the right
2068 # if we know window height, then that will set the lower text height, otherwise
2069 # we set lower text height which will drive window height
2070 if {[info exists geometry(main)]} {
2071 frame .bleft -width $geometry(botwidth)
2072 } else {
2073 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2075 frame .bleft.top
2076 frame .bleft.mid
2077 frame .bleft.bottom
2079 button .bleft.top.search -text [mc "Search"] -command dosearch
2080 pack .bleft.top.search -side left -padx 5
2081 set sstring .bleft.top.sstring
2082 entry $sstring -width 20 -font textfont -textvariable searchstring
2083 lappend entries $sstring
2084 trace add variable searchstring write incrsearch
2085 pack $sstring -side left -expand 1 -fill x
2086 radiobutton .bleft.mid.diff -text [mc "Diff"] \
2087 -command changediffdisp -variable diffelide -value {0 0}
2088 radiobutton .bleft.mid.old -text [mc "Old version"] \
2089 -command changediffdisp -variable diffelide -value {0 1}
2090 radiobutton .bleft.mid.new -text [mc "New version"] \
2091 -command changediffdisp -variable diffelide -value {1 0}
2092 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2093 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2094 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
2095 -from 1 -increment 1 -to 10000000 \
2096 -validate all -validatecommand "diffcontextvalidate %P" \
2097 -textvariable diffcontextstring
2098 .bleft.mid.diffcontext set $diffcontext
2099 trace add variable diffcontextstring write diffcontextchange
2100 lappend entries .bleft.mid.diffcontext
2101 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2102 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2103 -command changeignorespace -variable ignorespace
2104 pack .bleft.mid.ignspace -side left -padx 5
2105 set ctext .bleft.bottom.ctext
2106 text $ctext -background $bgcolor -foreground $fgcolor \
2107 -state disabled -font textfont \
2108 -yscrollcommand scrolltext -wrap none \
2109 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2110 if {$have_tk85} {
2111 $ctext conf -tabstyle wordprocessor
2113 scrollbar .bleft.bottom.sb -command "$ctext yview"
2114 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
2115 -width 10
2116 pack .bleft.top -side top -fill x
2117 pack .bleft.mid -side top -fill x
2118 grid $ctext .bleft.bottom.sb -sticky nsew
2119 grid .bleft.bottom.sbhorizontal -sticky ew
2120 grid columnconfigure .bleft.bottom 0 -weight 1
2121 grid rowconfigure .bleft.bottom 0 -weight 1
2122 grid rowconfigure .bleft.bottom 1 -weight 0
2123 pack .bleft.bottom -side top -fill both -expand 1
2124 lappend bglist $ctext
2125 lappend fglist $ctext
2127 $ctext tag conf comment -wrap $wrapcomment
2128 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2129 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2130 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2131 $ctext tag conf dresult -fore [lindex $diffcolors 1]
2132 $ctext tag conf m0 -fore red
2133 $ctext tag conf m1 -fore blue
2134 $ctext tag conf m2 -fore green
2135 $ctext tag conf m3 -fore purple
2136 $ctext tag conf m4 -fore brown
2137 $ctext tag conf m5 -fore "#009090"
2138 $ctext tag conf m6 -fore magenta
2139 $ctext tag conf m7 -fore "#808000"
2140 $ctext tag conf m8 -fore "#009000"
2141 $ctext tag conf m9 -fore "#ff0080"
2142 $ctext tag conf m10 -fore cyan
2143 $ctext tag conf m11 -fore "#b07070"
2144 $ctext tag conf m12 -fore "#70b0f0"
2145 $ctext tag conf m13 -fore "#70f0b0"
2146 $ctext tag conf m14 -fore "#f0b070"
2147 $ctext tag conf m15 -fore "#ff70b0"
2148 $ctext tag conf mmax -fore darkgrey
2149 set mergemax 16
2150 $ctext tag conf mresult -font textfontbold
2151 $ctext tag conf msep -font textfontbold
2152 $ctext tag conf found -back yellow
2154 .pwbottom add .bleft
2155 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2157 # lower right
2158 frame .bright
2159 frame .bright.mode
2160 radiobutton .bright.mode.patch -text [mc "Patch"] \
2161 -command reselectline -variable cmitmode -value "patch"
2162 radiobutton .bright.mode.tree -text [mc "Tree"] \
2163 -command reselectline -variable cmitmode -value "tree"
2164 grid .bright.mode.patch .bright.mode.tree -sticky ew
2165 pack .bright.mode -side top -fill x
2166 set cflist .bright.cfiles
2167 set indent [font measure mainfont "nn"]
2168 text $cflist \
2169 -selectbackground $selectbgcolor \
2170 -background $bgcolor -foreground $fgcolor \
2171 -font mainfont \
2172 -tabs [list $indent [expr {2 * $indent}]] \
2173 -yscrollcommand ".bright.sb set" \
2174 -cursor [. cget -cursor] \
2175 -spacing1 1 -spacing3 1
2176 lappend bglist $cflist
2177 lappend fglist $cflist
2178 scrollbar .bright.sb -command "$cflist yview"
2179 pack .bright.sb -side right -fill y
2180 pack $cflist -side left -fill both -expand 1
2181 $cflist tag configure highlight \
2182 -background [$cflist cget -selectbackground]
2183 $cflist tag configure bold -font mainfontbold
2185 .pwbottom add .bright
2186 .ctop add .pwbottom
2188 # restore window width & height if known
2189 if {[info exists geometry(main)]} {
2190 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2191 if {$w > [winfo screenwidth .]} {
2192 set w [winfo screenwidth .]
2194 if {$h > [winfo screenheight .]} {
2195 set h [winfo screenheight .]
2197 wm geometry . "${w}x$h"
2201 if {[tk windowingsystem] eq {aqua}} {
2202 set M1B M1
2203 } else {
2204 set M1B Control
2207 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2208 pack .ctop -fill both -expand 1
2209 bindall <1> {selcanvline %W %x %y}
2210 #bindall <B1-Motion> {selcanvline %W %x %y}
2211 if {[tk windowingsystem] == "win32"} {
2212 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2213 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2214 } else {
2215 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2216 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2217 if {[tk windowingsystem] eq "aqua"} {
2218 bindall <MouseWheel> {
2219 set delta [expr {- (%D)}]
2220 allcanvs yview scroll $delta units
2224 bindall <2> "canvscan mark %W %x %y"
2225 bindall <B2-Motion> "canvscan dragto %W %x %y"
2226 bindkey <Home> selfirstline
2227 bindkey <End> sellastline
2228 bind . <Key-Up> "selnextline -1"
2229 bind . <Key-Down> "selnextline 1"
2230 bind . <Shift-Key-Up> "dofind -1 0"
2231 bind . <Shift-Key-Down> "dofind 1 0"
2232 bindkey <Key-Right> "goforw"
2233 bindkey <Key-Left> "goback"
2234 bind . <Key-Prior> "selnextpage -1"
2235 bind . <Key-Next> "selnextpage 1"
2236 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2237 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2238 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2239 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2240 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2241 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2242 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2243 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2244 bindkey <Key-space> "$ctext yview scroll 1 pages"
2245 bindkey p "selnextline -1"
2246 bindkey n "selnextline 1"
2247 bindkey z "goback"
2248 bindkey x "goforw"
2249 bindkey i "selnextline -1"
2250 bindkey k "selnextline 1"
2251 bindkey j "goback"
2252 bindkey l "goforw"
2253 bindkey b prevfile
2254 bindkey d "$ctext yview scroll 18 units"
2255 bindkey u "$ctext yview scroll -18 units"
2256 bindkey / {dofind 1 1}
2257 bindkey <Key-Return> {dofind 1 1}
2258 bindkey ? {dofind -1 1}
2259 bindkey f nextfile
2260 bind . <F5> updatecommits
2261 bind . <$M1B-F5> reloadcommits
2262 bind . <F2> showrefs
2263 bind . <Shift-F4> {newview 0}
2264 catch { bind . <Shift-Key-XF86_Switch_VT_4> {newview 0} }
2265 bind . <F4> edit_or_newview
2266 bind . <$M1B-q> doquit
2267 bind . <$M1B-f> {dofind 1 1}
2268 bind . <$M1B-g> {dofind 1 0}
2269 bind . <$M1B-r> dosearchback
2270 bind . <$M1B-s> dosearch
2271 bind . <$M1B-equal> {incrfont 1}
2272 bind . <$M1B-plus> {incrfont 1}
2273 bind . <$M1B-KP_Add> {incrfont 1}
2274 bind . <$M1B-minus> {incrfont -1}
2275 bind . <$M1B-KP_Subtract> {incrfont -1}
2276 wm protocol . WM_DELETE_WINDOW doquit
2277 bind . <Destroy> {stop_backends}
2278 bind . <Button-1> "click %W"
2279 bind $fstring <Key-Return> {dofind 1 1}
2280 bind $sha1entry <Key-Return> {gotocommit; break}
2281 bind $sha1entry <<PasteSelection>> clearsha1
2282 bind $cflist <1> {sel_flist %W %x %y; break}
2283 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2284 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2285 global ctxbut
2286 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2287 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2289 set maincursor [. cget -cursor]
2290 set textcursor [$ctext cget -cursor]
2291 set curtextcursor $textcursor
2293 set rowctxmenu .rowctxmenu
2294 makemenu $rowctxmenu {
2295 {mc "Diff this -> selected" command {diffvssel 0}}
2296 {mc "Diff selected -> this" command {diffvssel 1}}
2297 {mc "Make patch" command mkpatch}
2298 {mc "Create tag" command mktag}
2299 {mc "Write commit to file" command writecommit}
2300 {mc "Create new branch" command mkbranch}
2301 {mc "Cherry-pick this commit" command cherrypick}
2302 {mc "Reset HEAD branch to here" command resethead}
2304 $rowctxmenu configure -tearoff 0
2306 set fakerowmenu .fakerowmenu
2307 makemenu $fakerowmenu {
2308 {mc "Diff this -> selected" command {diffvssel 0}}
2309 {mc "Diff selected -> this" command {diffvssel 1}}
2310 {mc "Make patch" command mkpatch}
2312 $fakerowmenu configure -tearoff 0
2314 set headctxmenu .headctxmenu
2315 makemenu $headctxmenu {
2316 {mc "Check out this branch" command cobranch}
2317 {mc "Remove this branch" command rmbranch}
2319 $headctxmenu configure -tearoff 0
2321 global flist_menu
2322 set flist_menu .flistctxmenu
2323 makemenu $flist_menu {
2324 {mc "Highlight this too" command {flist_hl 0}}
2325 {mc "Highlight this only" command {flist_hl 1}}
2326 {mc "External diff" command {external_diff}}
2327 {mc "Blame parent commit" command {external_blame 1}}
2329 $flist_menu configure -tearoff 0
2331 global diff_menu
2332 set diff_menu .diffctxmenu
2333 makemenu $diff_menu {
2334 {mc "Show origin of this line" command show_line_source}
2335 {mc "Run git gui blame on this line" command {external_blame_diff}}
2337 $diff_menu configure -tearoff 0
2340 # Windows sends all mouse wheel events to the current focused window, not
2341 # the one where the mouse hovers, so bind those events here and redirect
2342 # to the correct window
2343 proc windows_mousewheel_redirector {W X Y D} {
2344 global canv canv2 canv3
2345 set w [winfo containing -displayof $W $X $Y]
2346 if {$w ne ""} {
2347 set u [expr {$D < 0 ? 5 : -5}]
2348 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2349 allcanvs yview scroll $u units
2350 } else {
2351 catch {
2352 $w yview scroll $u units
2358 # Update row number label when selectedline changes
2359 proc selectedline_change {n1 n2 op} {
2360 global selectedline rownumsel
2362 if {$selectedline eq {}} {
2363 set rownumsel {}
2364 } else {
2365 set rownumsel [expr {$selectedline + 1}]
2369 # mouse-2 makes all windows scan vertically, but only the one
2370 # the cursor is in scans horizontally
2371 proc canvscan {op w x y} {
2372 global canv canv2 canv3
2373 foreach c [list $canv $canv2 $canv3] {
2374 if {$c == $w} {
2375 $c scan $op $x $y
2376 } else {
2377 $c scan $op 0 $y
2382 proc scrollcanv {cscroll f0 f1} {
2383 $cscroll set $f0 $f1
2384 drawvisible
2385 flushhighlights
2388 # when we make a key binding for the toplevel, make sure
2389 # it doesn't get triggered when that key is pressed in the
2390 # find string entry widget.
2391 proc bindkey {ev script} {
2392 global entries
2393 bind . $ev $script
2394 set escript [bind Entry $ev]
2395 if {$escript == {}} {
2396 set escript [bind Entry <Key>]
2398 foreach e $entries {
2399 bind $e $ev "$escript; break"
2403 # set the focus back to the toplevel for any click outside
2404 # the entry widgets
2405 proc click {w} {
2406 global ctext entries
2407 foreach e [concat $entries $ctext] {
2408 if {$w == $e} return
2410 focus .
2413 # Adjust the progress bar for a change in requested extent or canvas size
2414 proc adjustprogress {} {
2415 global progresscanv progressitem progresscoords
2416 global fprogitem fprogcoord lastprogupdate progupdatepending
2417 global rprogitem rprogcoord
2419 set w [expr {[winfo width $progresscanv] - 4}]
2420 set x0 [expr {$w * [lindex $progresscoords 0]}]
2421 set x1 [expr {$w * [lindex $progresscoords 1]}]
2422 set h [winfo height $progresscanv]
2423 $progresscanv coords $progressitem $x0 0 $x1 $h
2424 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2425 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2426 set now [clock clicks -milliseconds]
2427 if {$now >= $lastprogupdate + 100} {
2428 set progupdatepending 0
2429 update
2430 } elseif {!$progupdatepending} {
2431 set progupdatepending 1
2432 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2436 proc doprogupdate {} {
2437 global lastprogupdate progupdatepending
2439 if {$progupdatepending} {
2440 set progupdatepending 0
2441 set lastprogupdate [clock clicks -milliseconds]
2442 update
2446 proc savestuff {w} {
2447 global canv canv2 canv3 mainfont textfont uifont tabstop
2448 global stuffsaved findmergefiles maxgraphpct
2449 global maxwidth showneartags showlocalchanges
2450 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2451 global cmitmode wrapcomment datetimeformat limitdiffs
2452 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2453 global autoselect extdifftool perfile_attrs markbgcolor
2455 if {$stuffsaved} return
2456 if {![winfo viewable .]} return
2457 catch {
2458 set f [open "~/.gitk-new" w]
2459 puts $f [list set mainfont $mainfont]
2460 puts $f [list set textfont $textfont]
2461 puts $f [list set uifont $uifont]
2462 puts $f [list set tabstop $tabstop]
2463 puts $f [list set findmergefiles $findmergefiles]
2464 puts $f [list set maxgraphpct $maxgraphpct]
2465 puts $f [list set maxwidth $maxwidth]
2466 puts $f [list set cmitmode $cmitmode]
2467 puts $f [list set wrapcomment $wrapcomment]
2468 puts $f [list set autoselect $autoselect]
2469 puts $f [list set showneartags $showneartags]
2470 puts $f [list set showlocalchanges $showlocalchanges]
2471 puts $f [list set datetimeformat $datetimeformat]
2472 puts $f [list set limitdiffs $limitdiffs]
2473 puts $f [list set bgcolor $bgcolor]
2474 puts $f [list set fgcolor $fgcolor]
2475 puts $f [list set colors $colors]
2476 puts $f [list set diffcolors $diffcolors]
2477 puts $f [list set markbgcolor $markbgcolor]
2478 puts $f [list set diffcontext $diffcontext]
2479 puts $f [list set selectbgcolor $selectbgcolor]
2480 puts $f [list set extdifftool $extdifftool]
2481 puts $f [list set perfile_attrs $perfile_attrs]
2483 puts $f "set geometry(main) [wm geometry .]"
2484 puts $f "set geometry(topwidth) [winfo width .tf]"
2485 puts $f "set geometry(topheight) [winfo height .tf]"
2486 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2487 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2488 puts $f "set geometry(botwidth) [winfo width .bleft]"
2489 puts $f "set geometry(botheight) [winfo height .bleft]"
2491 puts -nonewline $f "set permviews {"
2492 for {set v 0} {$v < $nextviewnum} {incr v} {
2493 if {$viewperm($v)} {
2494 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2497 puts $f "}"
2498 close $f
2499 file rename -force "~/.gitk-new" "~/.gitk"
2501 set stuffsaved 1
2504 proc resizeclistpanes {win w} {
2505 global oldwidth
2506 if {[info exists oldwidth($win)]} {
2507 set s0 [$win sash coord 0]
2508 set s1 [$win sash coord 1]
2509 if {$w < 60} {
2510 set sash0 [expr {int($w/2 - 2)}]
2511 set sash1 [expr {int($w*5/6 - 2)}]
2512 } else {
2513 set factor [expr {1.0 * $w / $oldwidth($win)}]
2514 set sash0 [expr {int($factor * [lindex $s0 0])}]
2515 set sash1 [expr {int($factor * [lindex $s1 0])}]
2516 if {$sash0 < 30} {
2517 set sash0 30
2519 if {$sash1 < $sash0 + 20} {
2520 set sash1 [expr {$sash0 + 20}]
2522 if {$sash1 > $w - 10} {
2523 set sash1 [expr {$w - 10}]
2524 if {$sash0 > $sash1 - 20} {
2525 set sash0 [expr {$sash1 - 20}]
2529 $win sash place 0 $sash0 [lindex $s0 1]
2530 $win sash place 1 $sash1 [lindex $s1 1]
2532 set oldwidth($win) $w
2535 proc resizecdetpanes {win w} {
2536 global oldwidth
2537 if {[info exists oldwidth($win)]} {
2538 set s0 [$win sash coord 0]
2539 if {$w < 60} {
2540 set sash0 [expr {int($w*3/4 - 2)}]
2541 } else {
2542 set factor [expr {1.0 * $w / $oldwidth($win)}]
2543 set sash0 [expr {int($factor * [lindex $s0 0])}]
2544 if {$sash0 < 45} {
2545 set sash0 45
2547 if {$sash0 > $w - 15} {
2548 set sash0 [expr {$w - 15}]
2551 $win sash place 0 $sash0 [lindex $s0 1]
2553 set oldwidth($win) $w
2556 proc allcanvs args {
2557 global canv canv2 canv3
2558 eval $canv $args
2559 eval $canv2 $args
2560 eval $canv3 $args
2563 proc bindall {event action} {
2564 global canv canv2 canv3
2565 bind $canv $event $action
2566 bind $canv2 $event $action
2567 bind $canv3 $event $action
2570 proc about {} {
2571 global uifont
2572 set w .about
2573 if {[winfo exists $w]} {
2574 raise $w
2575 return
2577 toplevel $w
2578 wm title $w [mc "About gitk"]
2579 make_transient $w .
2580 message $w.m -text [mc "
2581 Gitk - a commit viewer for git
2583 Copyright © 2005-2008 Paul Mackerras
2585 Use and redistribute under the terms of the GNU General Public License"] \
2586 -justify center -aspect 400 -border 2 -bg white -relief groove
2587 pack $w.m -side top -fill x -padx 2 -pady 2
2588 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2589 pack $w.ok -side bottom
2590 bind $w <Visibility> "focus $w.ok"
2591 bind $w <Key-Escape> "destroy $w"
2592 bind $w <Key-Return> "destroy $w"
2595 proc keys {} {
2596 set w .keys
2597 if {[winfo exists $w]} {
2598 raise $w
2599 return
2601 if {[tk windowingsystem] eq {aqua}} {
2602 set M1T Cmd
2603 } else {
2604 set M1T Ctrl
2606 toplevel $w
2607 wm title $w [mc "Gitk key bindings"]
2608 make_transient $w .
2609 message $w.m -text "
2610 [mc "Gitk key bindings:"]
2612 [mc "<%s-Q> Quit" $M1T]
2613 [mc "<Home> Move to first commit"]
2614 [mc "<End> Move to last commit"]
2615 [mc "<Up>, p, i Move up one commit"]
2616 [mc "<Down>, n, k Move down one commit"]
2617 [mc "<Left>, z, j Go back in history list"]
2618 [mc "<Right>, x, l Go forward in history list"]
2619 [mc "<PageUp> Move up one page in commit list"]
2620 [mc "<PageDown> Move down one page in commit list"]
2621 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2622 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2623 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2624 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2625 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2626 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2627 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2628 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2629 [mc "<Delete>, b Scroll diff view up one page"]
2630 [mc "<Backspace> Scroll diff view up one page"]
2631 [mc "<Space> Scroll diff view down one page"]
2632 [mc "u Scroll diff view up 18 lines"]
2633 [mc "d Scroll diff view down 18 lines"]
2634 [mc "<%s-F> Find" $M1T]
2635 [mc "<%s-G> Move to next find hit" $M1T]
2636 [mc "<Return> Move to next find hit"]
2637 [mc "/ Move to next find hit, or redo find"]
2638 [mc "? Move to previous find hit"]
2639 [mc "f Scroll diff view to next file"]
2640 [mc "<%s-S> Search for next hit in diff view" $M1T]
2641 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2642 [mc "<%s-KP+> Increase font size" $M1T]
2643 [mc "<%s-plus> Increase font size" $M1T]
2644 [mc "<%s-KP-> Decrease font size" $M1T]
2645 [mc "<%s-minus> Decrease font size" $M1T]
2646 [mc "<F5> Update"]
2648 -justify left -bg white -border 2 -relief groove
2649 pack $w.m -side top -fill both -padx 2 -pady 2
2650 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2651 bind $w <Key-Escape> [list destroy $w]
2652 pack $w.ok -side bottom
2653 bind $w <Visibility> "focus $w.ok"
2654 bind $w <Key-Escape> "destroy $w"
2655 bind $w <Key-Return> "destroy $w"
2658 # Procedures for manipulating the file list window at the
2659 # bottom right of the overall window.
2661 proc treeview {w l openlevs} {
2662 global treecontents treediropen treeheight treeparent treeindex
2664 set ix 0
2665 set treeindex() 0
2666 set lev 0
2667 set prefix {}
2668 set prefixend -1
2669 set prefendstack {}
2670 set htstack {}
2671 set ht 0
2672 set treecontents() {}
2673 $w conf -state normal
2674 foreach f $l {
2675 while {[string range $f 0 $prefixend] ne $prefix} {
2676 if {$lev <= $openlevs} {
2677 $w mark set e:$treeindex($prefix) "end -1c"
2678 $w mark gravity e:$treeindex($prefix) left
2680 set treeheight($prefix) $ht
2681 incr ht [lindex $htstack end]
2682 set htstack [lreplace $htstack end end]
2683 set prefixend [lindex $prefendstack end]
2684 set prefendstack [lreplace $prefendstack end end]
2685 set prefix [string range $prefix 0 $prefixend]
2686 incr lev -1
2688 set tail [string range $f [expr {$prefixend+1}] end]
2689 while {[set slash [string first "/" $tail]] >= 0} {
2690 lappend htstack $ht
2691 set ht 0
2692 lappend prefendstack $prefixend
2693 incr prefixend [expr {$slash + 1}]
2694 set d [string range $tail 0 $slash]
2695 lappend treecontents($prefix) $d
2696 set oldprefix $prefix
2697 append prefix $d
2698 set treecontents($prefix) {}
2699 set treeindex($prefix) [incr ix]
2700 set treeparent($prefix) $oldprefix
2701 set tail [string range $tail [expr {$slash+1}] end]
2702 if {$lev <= $openlevs} {
2703 set ht 1
2704 set treediropen($prefix) [expr {$lev < $openlevs}]
2705 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2706 $w mark set d:$ix "end -1c"
2707 $w mark gravity d:$ix left
2708 set str "\n"
2709 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2710 $w insert end $str
2711 $w image create end -align center -image $bm -padx 1 \
2712 -name a:$ix
2713 $w insert end $d [highlight_tag $prefix]
2714 $w mark set s:$ix "end -1c"
2715 $w mark gravity s:$ix left
2717 incr lev
2719 if {$tail ne {}} {
2720 if {$lev <= $openlevs} {
2721 incr ht
2722 set str "\n"
2723 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2724 $w insert end $str
2725 $w insert end $tail [highlight_tag $f]
2727 lappend treecontents($prefix) $tail
2730 while {$htstack ne {}} {
2731 set treeheight($prefix) $ht
2732 incr ht [lindex $htstack end]
2733 set htstack [lreplace $htstack end end]
2734 set prefixend [lindex $prefendstack end]
2735 set prefendstack [lreplace $prefendstack end end]
2736 set prefix [string range $prefix 0 $prefixend]
2738 $w conf -state disabled
2741 proc linetoelt {l} {
2742 global treeheight treecontents
2744 set y 2
2745 set prefix {}
2746 while {1} {
2747 foreach e $treecontents($prefix) {
2748 if {$y == $l} {
2749 return "$prefix$e"
2751 set n 1
2752 if {[string index $e end] eq "/"} {
2753 set n $treeheight($prefix$e)
2754 if {$y + $n > $l} {
2755 append prefix $e
2756 incr y
2757 break
2760 incr y $n
2765 proc highlight_tree {y prefix} {
2766 global treeheight treecontents cflist
2768 foreach e $treecontents($prefix) {
2769 set path $prefix$e
2770 if {[highlight_tag $path] ne {}} {
2771 $cflist tag add bold $y.0 "$y.0 lineend"
2773 incr y
2774 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2775 set y [highlight_tree $y $path]
2778 return $y
2781 proc treeclosedir {w dir} {
2782 global treediropen treeheight treeparent treeindex
2784 set ix $treeindex($dir)
2785 $w conf -state normal
2786 $w delete s:$ix e:$ix
2787 set treediropen($dir) 0
2788 $w image configure a:$ix -image tri-rt
2789 $w conf -state disabled
2790 set n [expr {1 - $treeheight($dir)}]
2791 while {$dir ne {}} {
2792 incr treeheight($dir) $n
2793 set dir $treeparent($dir)
2797 proc treeopendir {w dir} {
2798 global treediropen treeheight treeparent treecontents treeindex
2800 set ix $treeindex($dir)
2801 $w conf -state normal
2802 $w image configure a:$ix -image tri-dn
2803 $w mark set e:$ix s:$ix
2804 $w mark gravity e:$ix right
2805 set lev 0
2806 set str "\n"
2807 set n [llength $treecontents($dir)]
2808 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2809 incr lev
2810 append str "\t"
2811 incr treeheight($x) $n
2813 foreach e $treecontents($dir) {
2814 set de $dir$e
2815 if {[string index $e end] eq "/"} {
2816 set iy $treeindex($de)
2817 $w mark set d:$iy e:$ix
2818 $w mark gravity d:$iy left
2819 $w insert e:$ix $str
2820 set treediropen($de) 0
2821 $w image create e:$ix -align center -image tri-rt -padx 1 \
2822 -name a:$iy
2823 $w insert e:$ix $e [highlight_tag $de]
2824 $w mark set s:$iy e:$ix
2825 $w mark gravity s:$iy left
2826 set treeheight($de) 1
2827 } else {
2828 $w insert e:$ix $str
2829 $w insert e:$ix $e [highlight_tag $de]
2832 $w mark gravity e:$ix right
2833 $w conf -state disabled
2834 set treediropen($dir) 1
2835 set top [lindex [split [$w index @0,0] .] 0]
2836 set ht [$w cget -height]
2837 set l [lindex [split [$w index s:$ix] .] 0]
2838 if {$l < $top} {
2839 $w yview $l.0
2840 } elseif {$l + $n + 1 > $top + $ht} {
2841 set top [expr {$l + $n + 2 - $ht}]
2842 if {$l < $top} {
2843 set top $l
2845 $w yview $top.0
2849 proc treeclick {w x y} {
2850 global treediropen cmitmode ctext cflist cflist_top
2852 if {$cmitmode ne "tree"} return
2853 if {![info exists cflist_top]} return
2854 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2855 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2856 $cflist tag add highlight $l.0 "$l.0 lineend"
2857 set cflist_top $l
2858 if {$l == 1} {
2859 $ctext yview 1.0
2860 return
2862 set e [linetoelt $l]
2863 if {[string index $e end] ne "/"} {
2864 showfile $e
2865 } elseif {$treediropen($e)} {
2866 treeclosedir $w $e
2867 } else {
2868 treeopendir $w $e
2872 proc setfilelist {id} {
2873 global treefilelist cflist jump_to_here
2875 treeview $cflist $treefilelist($id) 0
2876 if {$jump_to_here ne {}} {
2877 set f [lindex $jump_to_here 0]
2878 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
2879 showfile $f
2884 image create bitmap tri-rt -background black -foreground blue -data {
2885 #define tri-rt_width 13
2886 #define tri-rt_height 13
2887 static unsigned char tri-rt_bits[] = {
2888 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2889 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2890 0x00, 0x00};
2891 } -maskdata {
2892 #define tri-rt-mask_width 13
2893 #define tri-rt-mask_height 13
2894 static unsigned char tri-rt-mask_bits[] = {
2895 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2896 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2897 0x08, 0x00};
2899 image create bitmap tri-dn -background black -foreground blue -data {
2900 #define tri-dn_width 13
2901 #define tri-dn_height 13
2902 static unsigned char tri-dn_bits[] = {
2903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2904 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2905 0x00, 0x00};
2906 } -maskdata {
2907 #define tri-dn-mask_width 13
2908 #define tri-dn-mask_height 13
2909 static unsigned char tri-dn-mask_bits[] = {
2910 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2911 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2912 0x00, 0x00};
2915 image create bitmap reficon-T -background black -foreground yellow -data {
2916 #define tagicon_width 13
2917 #define tagicon_height 9
2918 static unsigned char tagicon_bits[] = {
2919 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2920 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2921 } -maskdata {
2922 #define tagicon-mask_width 13
2923 #define tagicon-mask_height 9
2924 static unsigned char tagicon-mask_bits[] = {
2925 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2926 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2928 set rectdata {
2929 #define headicon_width 13
2930 #define headicon_height 9
2931 static unsigned char headicon_bits[] = {
2932 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2933 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2935 set rectmask {
2936 #define headicon-mask_width 13
2937 #define headicon-mask_height 9
2938 static unsigned char headicon-mask_bits[] = {
2939 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2940 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2942 image create bitmap reficon-H -background black -foreground green \
2943 -data $rectdata -maskdata $rectmask
2944 image create bitmap reficon-o -background black -foreground "#ddddff" \
2945 -data $rectdata -maskdata $rectmask
2947 proc init_flist {first} {
2948 global cflist cflist_top difffilestart
2950 $cflist conf -state normal
2951 $cflist delete 0.0 end
2952 if {$first ne {}} {
2953 $cflist insert end $first
2954 set cflist_top 1
2955 $cflist tag add highlight 1.0 "1.0 lineend"
2956 } else {
2957 catch {unset cflist_top}
2959 $cflist conf -state disabled
2960 set difffilestart {}
2963 proc highlight_tag {f} {
2964 global highlight_paths
2966 foreach p $highlight_paths {
2967 if {[string match $p $f]} {
2968 return "bold"
2971 return {}
2974 proc highlight_filelist {} {
2975 global cmitmode cflist
2977 $cflist conf -state normal
2978 if {$cmitmode ne "tree"} {
2979 set end [lindex [split [$cflist index end] .] 0]
2980 for {set l 2} {$l < $end} {incr l} {
2981 set line [$cflist get $l.0 "$l.0 lineend"]
2982 if {[highlight_tag $line] ne {}} {
2983 $cflist tag add bold $l.0 "$l.0 lineend"
2986 } else {
2987 highlight_tree 2 {}
2989 $cflist conf -state disabled
2992 proc unhighlight_filelist {} {
2993 global cflist
2995 $cflist conf -state normal
2996 $cflist tag remove bold 1.0 end
2997 $cflist conf -state disabled
3000 proc add_flist {fl} {
3001 global cflist
3003 $cflist conf -state normal
3004 foreach f $fl {
3005 $cflist insert end "\n"
3006 $cflist insert end $f [highlight_tag $f]
3008 $cflist conf -state disabled
3011 proc sel_flist {w x y} {
3012 global ctext difffilestart cflist cflist_top cmitmode
3014 if {$cmitmode eq "tree"} return
3015 if {![info exists cflist_top]} return
3016 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3017 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3018 $cflist tag add highlight $l.0 "$l.0 lineend"
3019 set cflist_top $l
3020 if {$l == 1} {
3021 $ctext yview 1.0
3022 } else {
3023 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
3027 proc pop_flist_menu {w X Y x y} {
3028 global ctext cflist cmitmode flist_menu flist_menu_file
3029 global treediffs diffids
3031 stopfinding
3032 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3033 if {$l <= 1} return
3034 if {$cmitmode eq "tree"} {
3035 set e [linetoelt $l]
3036 if {[string index $e end] eq "/"} return
3037 } else {
3038 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3040 set flist_menu_file $e
3041 set xdiffstate "normal"
3042 if {$cmitmode eq "tree"} {
3043 set xdiffstate "disabled"
3045 # Disable "External diff" item in tree mode
3046 $flist_menu entryconf 2 -state $xdiffstate
3047 tk_popup $flist_menu $X $Y
3050 proc find_ctext_fileinfo {line} {
3051 global ctext_file_names ctext_file_lines
3053 set ok [bsearch $ctext_file_lines $line]
3054 set tline [lindex $ctext_file_lines $ok]
3056 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3057 return {}
3058 } else {
3059 return [list [lindex $ctext_file_names $ok] $tline]
3063 proc pop_diff_menu {w X Y x y} {
3064 global ctext diff_menu flist_menu_file
3065 global diff_menu_txtpos diff_menu_line
3066 global diff_menu_filebase
3068 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3069 set diff_menu_line [lindex $diff_menu_txtpos 0]
3070 # don't pop up the menu on hunk-separator or file-separator lines
3071 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3072 return
3074 stopfinding
3075 set f [find_ctext_fileinfo $diff_menu_line]
3076 if {$f eq {}} return
3077 set flist_menu_file [lindex $f 0]
3078 set diff_menu_filebase [lindex $f 1]
3079 tk_popup $diff_menu $X $Y
3082 proc flist_hl {only} {
3083 global flist_menu_file findstring gdttype
3085 set x [shellquote $flist_menu_file]
3086 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3087 set findstring $x
3088 } else {
3089 append findstring " " $x
3091 set gdttype [mc "touching paths:"]
3094 proc save_file_from_commit {filename output what} {
3095 global nullfile
3097 if {[catch {exec git show $filename -- > $output} err]} {
3098 if {[string match "fatal: bad revision *" $err]} {
3099 return $nullfile
3101 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3102 return {}
3104 return $output
3107 proc external_diff_get_one_file {diffid filename diffdir} {
3108 global nullid nullid2 nullfile
3109 global gitdir
3111 if {$diffid == $nullid} {
3112 set difffile [file join [file dirname $gitdir] $filename]
3113 if {[file exists $difffile]} {
3114 return $difffile
3116 return $nullfile
3118 if {$diffid == $nullid2} {
3119 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3120 return [save_file_from_commit :$filename $difffile index]
3122 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3123 return [save_file_from_commit $diffid:$filename $difffile \
3124 "revision $diffid"]
3127 proc external_diff {} {
3128 global gitktmpdir nullid nullid2
3129 global flist_menu_file
3130 global diffids
3131 global diffnum
3132 global gitdir extdifftool
3134 if {[llength $diffids] == 1} {
3135 # no reference commit given
3136 set diffidto [lindex $diffids 0]
3137 if {$diffidto eq $nullid} {
3138 # diffing working copy with index
3139 set diffidfrom $nullid2
3140 } elseif {$diffidto eq $nullid2} {
3141 # diffing index with HEAD
3142 set diffidfrom "HEAD"
3143 } else {
3144 # use first parent commit
3145 global parentlist selectedline
3146 set diffidfrom [lindex $parentlist $selectedline 0]
3148 } else {
3149 set diffidfrom [lindex $diffids 0]
3150 set diffidto [lindex $diffids 1]
3153 # make sure that several diffs wont collide
3154 if {![info exists gitktmpdir]} {
3155 set gitktmpdir [file join [file dirname $gitdir] \
3156 [format ".gitk-tmp.%s" [pid]]]
3157 if {[catch {file mkdir $gitktmpdir} err]} {
3158 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3159 unset gitktmpdir
3160 return
3162 set diffnum 0
3164 incr diffnum
3165 set diffdir [file join $gitktmpdir $diffnum]
3166 if {[catch {file mkdir $diffdir} err]} {
3167 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3168 return
3171 # gather files to diff
3172 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3173 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3175 if {$difffromfile ne {} && $difftofile ne {}} {
3176 set cmd [concat | [shellsplit $extdifftool] \
3177 [list $difffromfile $difftofile]]
3178 if {[catch {set fl [open $cmd r]} err]} {
3179 file delete -force $diffdir
3180 error_popup "$extdifftool: [mc "command failed:"] $err"
3181 } else {
3182 fconfigure $fl -blocking 0
3183 filerun $fl [list delete_at_eof $fl $diffdir]
3188 proc find_hunk_blamespec {base line} {
3189 global ctext
3191 # Find and parse the hunk header
3192 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3193 if {$s_lix eq {}} return
3195 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3196 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3197 s_line old_specs osz osz1 new_line nsz]} {
3198 return
3201 # base lines for the parents
3202 set base_lines [list $new_line]
3203 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3204 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3205 old_spec old_line osz]} {
3206 return
3208 lappend base_lines $old_line
3211 # Now scan the lines to determine offset within the hunk
3212 set max_parent [expr {[llength $base_lines]-2}]
3213 set dline 0
3214 set s_lno [lindex [split $s_lix "."] 0]
3216 # Determine if the line is removed
3217 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3218 if {[string match {[-+ ]*} $chunk]} {
3219 set removed_idx [string first "-" $chunk]
3220 # Choose a parent index
3221 if {$removed_idx >= 0} {
3222 set parent $removed_idx
3223 } else {
3224 set unchanged_idx [string first " " $chunk]
3225 if {$unchanged_idx >= 0} {
3226 set parent $unchanged_idx
3227 } else {
3228 # blame the current commit
3229 set parent -1
3232 # then count other lines that belong to it
3233 for {set i $line} {[incr i -1] > $s_lno} {} {
3234 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3235 # Determine if the line is removed
3236 set removed_idx [string first "-" $chunk]
3237 if {$parent >= 0} {
3238 set code [string index $chunk $parent]
3239 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3240 incr dline
3242 } else {
3243 if {$removed_idx < 0} {
3244 incr dline
3248 incr parent
3249 } else {
3250 set parent 0
3253 incr dline [lindex $base_lines $parent]
3254 return [list $parent $dline]
3257 proc external_blame_diff {} {
3258 global currentid cmitmode
3259 global diff_menu_txtpos diff_menu_line
3260 global diff_menu_filebase flist_menu_file
3262 if {$cmitmode eq "tree"} {
3263 set parent_idx 0
3264 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3265 } else {
3266 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3267 if {$hinfo ne {}} {
3268 set parent_idx [lindex $hinfo 0]
3269 set line [lindex $hinfo 1]
3270 } else {
3271 set parent_idx 0
3272 set line 0
3276 external_blame $parent_idx $line
3279 # Find the SHA1 ID of the blob for file $fname in the index
3280 # at stage 0 or 2
3281 proc index_sha1 {fname} {
3282 set f [open [list | git ls-files -s $fname] r]
3283 while {[gets $f line] >= 0} {
3284 set info [lindex [split $line "\t"] 0]
3285 set stage [lindex $info 2]
3286 if {$stage eq "0" || $stage eq "2"} {
3287 close $f
3288 return [lindex $info 1]
3291 close $f
3292 return {}
3295 proc external_blame {parent_idx {line {}}} {
3296 global flist_menu_file
3297 global nullid nullid2
3298 global parentlist selectedline currentid
3300 if {$parent_idx > 0} {
3301 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3302 } else {
3303 set base_commit $currentid
3306 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3307 error_popup [mc "No such commit"]
3308 return
3311 set cmdline [list git gui blame]
3312 if {$line ne {} && $line > 1} {
3313 lappend cmdline "--line=$line"
3315 lappend cmdline $base_commit $flist_menu_file
3316 if {[catch {eval exec $cmdline &} err]} {
3317 error_popup "[mc "git gui blame: command failed:"] $err"
3321 proc show_line_source {} {
3322 global cmitmode currentid parents curview blamestuff blameinst
3323 global diff_menu_line diff_menu_filebase flist_menu_file
3324 global nullid nullid2 gitdir
3326 set from_index {}
3327 if {$cmitmode eq "tree"} {
3328 set id $currentid
3329 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3330 } else {
3331 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3332 if {$h eq {}} return
3333 set pi [lindex $h 0]
3334 if {$pi == 0} {
3335 mark_ctext_line $diff_menu_line
3336 return
3338 incr pi -1
3339 if {$currentid eq $nullid} {
3340 if {$pi > 0} {
3341 # must be a merge in progress...
3342 if {[catch {
3343 # get the last line from .git/MERGE_HEAD
3344 set f [open [file join $gitdir MERGE_HEAD] r]
3345 set id [lindex [split [read $f] "\n"] end-1]
3346 close $f
3347 } err]} {
3348 error_popup [mc "Couldn't read merge head: %s" $err]
3349 return
3351 } elseif {$parents($curview,$currentid) eq $nullid2} {
3352 # need to do the blame from the index
3353 if {[catch {
3354 set from_index [index_sha1 $flist_menu_file]
3355 } err]} {
3356 error_popup [mc "Error reading index: %s" $err]
3357 return
3360 } else {
3361 set id [lindex $parents($curview,$currentid) $pi]
3363 set line [lindex $h 1]
3365 set blameargs {}
3366 if {$from_index ne {}} {
3367 lappend blameargs | git cat-file blob $from_index
3369 lappend blameargs | git blame -p -L$line,+1
3370 if {$from_index ne {}} {
3371 lappend blameargs --contents -
3372 } else {
3373 lappend blameargs $id
3375 lappend blameargs -- $flist_menu_file
3376 if {[catch {
3377 set f [open $blameargs r]
3378 } err]} {
3379 error_popup [mc "Couldn't start git blame: %s" $err]
3380 return
3382 fconfigure $f -blocking 0
3383 set i [reg_instance $f]
3384 set blamestuff($i) {}
3385 set blameinst $i
3386 filerun $f [list read_line_source $f $i]
3389 proc stopblaming {} {
3390 global blameinst
3392 if {[info exists blameinst]} {
3393 stop_instance $blameinst
3394 unset blameinst
3398 proc read_line_source {fd inst} {
3399 global blamestuff curview commfd blameinst nullid nullid2
3401 while {[gets $fd line] >= 0} {
3402 lappend blamestuff($inst) $line
3404 if {![eof $fd]} {
3405 return 1
3407 unset commfd($inst)
3408 unset blameinst
3409 fconfigure $fd -blocking 1
3410 if {[catch {close $fd} err]} {
3411 error_popup [mc "Error running git blame: %s" $err]
3412 return 0
3415 set fname {}
3416 set line [split [lindex $blamestuff($inst) 0] " "]
3417 set id [lindex $line 0]
3418 set lnum [lindex $line 1]
3419 if {[string length $id] == 40 && [string is xdigit $id] &&
3420 [string is digit -strict $lnum]} {
3421 # look for "filename" line
3422 foreach l $blamestuff($inst) {
3423 if {[string match "filename *" $l]} {
3424 set fname [string range $l 9 end]
3425 break
3429 if {$fname ne {}} {
3430 # all looks good, select it
3431 if {$id eq $nullid} {
3432 # blame uses all-zeroes to mean not committed,
3433 # which would mean a change in the index
3434 set id $nullid2
3436 if {[commitinview $id $curview]} {
3437 selectline [rowofcommit $id] 1 [list $fname $lnum]
3438 } else {
3439 error_popup [mc "That line comes from commit %s, \
3440 which is not in this view" [shortids $id]]
3442 } else {
3443 puts "oops couldn't parse git blame output"
3445 return 0
3448 # delete $dir when we see eof on $f (presumably because the child has exited)
3449 proc delete_at_eof {f dir} {
3450 while {[gets $f line] >= 0} {}
3451 if {[eof $f]} {
3452 if {[catch {close $f} err]} {
3453 error_popup "[mc "External diff viewer failed:"] $err"
3455 file delete -force $dir
3456 return 0
3458 return 1
3461 # Functions for adding and removing shell-type quoting
3463 proc shellquote {str} {
3464 if {![string match "*\['\"\\ \t]*" $str]} {
3465 return $str
3467 if {![string match "*\['\"\\]*" $str]} {
3468 return "\"$str\""
3470 if {![string match "*'*" $str]} {
3471 return "'$str'"
3473 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3476 proc shellarglist {l} {
3477 set str {}
3478 foreach a $l {
3479 if {$str ne {}} {
3480 append str " "
3482 append str [shellquote $a]
3484 return $str
3487 proc shelldequote {str} {
3488 set ret {}
3489 set used -1
3490 while {1} {
3491 incr used
3492 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3493 append ret [string range $str $used end]
3494 set used [string length $str]
3495 break
3497 set first [lindex $first 0]
3498 set ch [string index $str $first]
3499 if {$first > $used} {
3500 append ret [string range $str $used [expr {$first - 1}]]
3501 set used $first
3503 if {$ch eq " " || $ch eq "\t"} break
3504 incr used
3505 if {$ch eq "'"} {
3506 set first [string first "'" $str $used]
3507 if {$first < 0} {
3508 error "unmatched single-quote"
3510 append ret [string range $str $used [expr {$first - 1}]]
3511 set used $first
3512 continue
3514 if {$ch eq "\\"} {
3515 if {$used >= [string length $str]} {
3516 error "trailing backslash"
3518 append ret [string index $str $used]
3519 continue
3521 # here ch == "\""
3522 while {1} {
3523 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3524 error "unmatched double-quote"
3526 set first [lindex $first 0]
3527 set ch [string index $str $first]
3528 if {$first > $used} {
3529 append ret [string range $str $used [expr {$first - 1}]]
3530 set used $first
3532 if {$ch eq "\""} break
3533 incr used
3534 append ret [string index $str $used]
3535 incr used
3538 return [list $used $ret]
3541 proc shellsplit {str} {
3542 set l {}
3543 while {1} {
3544 set str [string trimleft $str]
3545 if {$str eq {}} break
3546 set dq [shelldequote $str]
3547 set n [lindex $dq 0]
3548 set word [lindex $dq 1]
3549 set str [string range $str $n end]
3550 lappend l $word
3552 return $l
3555 # Code to implement multiple views
3557 proc newview {ishighlight} {
3558 global nextviewnum newviewname newishighlight
3559 global revtreeargs viewargscmd newviewopts curview
3561 set newishighlight $ishighlight
3562 set top .gitkview
3563 if {[winfo exists $top]} {
3564 raise $top
3565 return
3567 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3568 set newviewopts($nextviewnum,perm) 0
3569 set newviewopts($nextviewnum,cmd) $viewargscmd($curview)
3570 decode_view_opts $nextviewnum $revtreeargs
3571 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3574 set known_view_options {
3575 {perm b . {} {mc "Remember this view"}}
3576 {args t50= + {} {mc "Commits to include (arguments to git log):"}}
3577 {all b * "--all" {mc "Use all refs"}}
3578 {dorder b . {"--date-order" "-d"} {mc "Strictly sort by date"}}
3579 {lright b . "--left-right" {mc "Mark branch sides"}}
3580 {since t15 + {"--since=*" "--after=*"} {mc "Since date:"}}
3581 {until t15 . {"--until=*" "--before=*"} {mc "Until date:"}}
3582 {limit t10 + "--max-count=*" {mc "Max count:"}}
3583 {skip t10 . "--skip=*" {mc "Skip:"}}
3584 {first b . "--first-parent" {mc "Limit to first parent"}}
3585 {cmd t50= + {} {mc "Command to generate more commits to include:"}}
3588 proc encode_view_opts {n} {
3589 global known_view_options newviewopts
3591 set rargs [list]
3592 foreach opt $known_view_options {
3593 set patterns [lindex $opt 3]
3594 if {$patterns eq {}} continue
3595 set pattern [lindex $patterns 0]
3597 set val $newviewopts($n,[lindex $opt 0])
3599 if {[lindex $opt 1] eq "b"} {
3600 if {$val} {
3601 lappend rargs $pattern
3603 } else {
3604 set val [string trim $val]
3605 if {$val ne {}} {
3606 set pfix [string range $pattern 0 end-1]
3607 lappend rargs $pfix$val
3611 return [concat $rargs [shellsplit $newviewopts($n,args)]]
3614 proc decode_view_opts {n view_args} {
3615 global known_view_options newviewopts
3617 foreach opt $known_view_options {
3618 if {[lindex $opt 1] eq "b"} {
3619 set val 0
3620 } else {
3621 set val {}
3623 set newviewopts($n,[lindex $opt 0]) $val
3625 set oargs [list]
3626 foreach arg $view_args {
3627 if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3628 && ![info exists found(limit)]} {
3629 set newviewopts($n,limit) $cnt
3630 set found(limit) 1
3631 continue
3633 catch { unset val }
3634 foreach opt $known_view_options {
3635 set id [lindex $opt 0]
3636 if {[info exists found($id)]} continue
3637 foreach pattern [lindex $opt 3] {
3638 if {![string match $pattern $arg]} continue
3639 if {[lindex $opt 1] ne "b"} {
3640 set size [string length $pattern]
3641 set val [string range $arg [expr {$size-1}] end]
3642 } else {
3643 set val 1
3645 set newviewopts($n,$id) $val
3646 set found($id) 1
3647 break
3649 if {[info exists val]} break
3651 if {[info exists val]} continue
3652 lappend oargs $arg
3654 set newviewopts($n,args) [shellarglist $oargs]
3657 proc edit_or_newview {} {
3658 global curview
3660 if {$curview > 0} {
3661 editview
3662 } else {
3663 newview 0
3667 proc editview {} {
3668 global curview
3669 global viewname viewperm newviewname newviewopts
3670 global viewargs viewargscmd
3672 set top .gitkvedit-$curview
3673 if {[winfo exists $top]} {
3674 raise $top
3675 return
3677 set newviewname($curview) $viewname($curview)
3678 set newviewopts($curview,perm) $viewperm($curview)
3679 set newviewopts($curview,cmd) $viewargscmd($curview)
3680 decode_view_opts $curview $viewargs($curview)
3681 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3684 proc vieweditor {top n title} {
3685 global newviewname newviewopts viewfiles bgcolor
3686 global known_view_options
3688 toplevel $top
3689 wm title $top $title
3690 make_transient $top .
3692 # View name
3693 frame $top.nfr
3694 label $top.nl -text [mc "Name"]
3695 entry $top.name -width 20 -textvariable newviewname($n)
3696 pack $top.nfr -in $top -fill x -pady 5 -padx 3
3697 pack $top.nl -in $top.nfr -side left -padx {0 30}
3698 pack $top.name -in $top.nfr -side left
3700 # View options
3701 set cframe $top.nfr
3702 set cexpand 0
3703 set cnt 0
3704 foreach opt $known_view_options {
3705 set id [lindex $opt 0]
3706 set type [lindex $opt 1]
3707 set flags [lindex $opt 2]
3708 set title [eval [lindex $opt 4]]
3709 set lxpad 0
3711 if {$flags eq "+" || $flags eq "*"} {
3712 set cframe $top.fr$cnt
3713 incr cnt
3714 frame $cframe
3715 pack $cframe -in $top -fill x -pady 3 -padx 3
3716 set cexpand [expr {$flags eq "*"}]
3717 } else {
3718 set lxpad 5
3721 if {$type eq "b"} {
3722 checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
3723 pack $cframe.c_$id -in $cframe -side left \
3724 -padx [list $lxpad 0] -expand $cexpand -anchor w
3725 } elseif {[regexp {^t(\d+)$} $type type sz]} {
3726 message $cframe.l_$id -aspect 1500 -text $title
3727 entry $cframe.e_$id -width $sz -background $bgcolor \
3728 -textvariable newviewopts($n,$id)
3729 pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
3730 pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
3731 } elseif {[regexp {^t(\d+)=$} $type type sz]} {
3732 message $cframe.l_$id -aspect 1500 -text $title
3733 entry $cframe.e_$id -width $sz -background $bgcolor \
3734 -textvariable newviewopts($n,$id)
3735 pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
3736 pack $cframe.e_$id -in $cframe -side top -fill x
3740 # Path list
3741 message $top.l -aspect 1500 \
3742 -text [mc "Enter files and directories to include, one per line:"]
3743 pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3
3744 text $top.t -width 40 -height 5 -background $bgcolor -font uifont
3745 if {[info exists viewfiles($n)]} {
3746 foreach f $viewfiles($n) {
3747 $top.t insert end $f
3748 $top.t insert end "\n"
3750 $top.t delete {end - 1c} end
3751 $top.t mark set insert 0.0
3753 pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
3754 frame $top.buts
3755 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3756 button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
3757 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3758 bind $top <Control-Return> [list newviewok $top $n]
3759 bind $top <F5> [list newviewok $top $n 1]
3760 bind $top <Escape> [list destroy $top]
3761 grid $top.buts.ok $top.buts.apply $top.buts.can
3762 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3763 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3764 grid columnconfigure $top.buts 2 -weight 1 -uniform a
3765 pack $top.buts -in $top -side top -fill x
3766 focus $top.t
3769 proc doviewmenu {m first cmd op argv} {
3770 set nmenu [$m index end]
3771 for {set i $first} {$i <= $nmenu} {incr i} {
3772 if {[$m entrycget $i -command] eq $cmd} {
3773 eval $m $op $i $argv
3774 break
3779 proc allviewmenus {n op args} {
3780 # global viewhlmenu
3782 doviewmenu .bar.view 5 [list showview $n] $op $args
3783 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3786 proc newviewok {top n {apply 0}} {
3787 global nextviewnum newviewperm newviewname newishighlight
3788 global viewname viewfiles viewperm selectedview curview
3789 global viewargs viewargscmd newviewopts viewhlmenu
3791 if {[catch {
3792 set newargs [encode_view_opts $n]
3793 } err]} {
3794 error_popup "[mc "Error in commit selection arguments:"] $err" $top
3795 return
3797 set files {}
3798 foreach f [split [$top.t get 0.0 end] "\n"] {
3799 set ft [string trim $f]
3800 if {$ft ne {}} {
3801 lappend files $ft
3804 if {![info exists viewfiles($n)]} {
3805 # creating a new view
3806 incr nextviewnum
3807 set viewname($n) $newviewname($n)
3808 set viewperm($n) $newviewopts($n,perm)
3809 set viewfiles($n) $files
3810 set viewargs($n) $newargs
3811 set viewargscmd($n) $newviewopts($n,cmd)
3812 addviewmenu $n
3813 if {!$newishighlight} {
3814 run showview $n
3815 } else {
3816 run addvhighlight $n
3818 } else {
3819 # editing an existing view
3820 set viewperm($n) $newviewopts($n,perm)
3821 if {$newviewname($n) ne $viewname($n)} {
3822 set viewname($n) $newviewname($n)
3823 doviewmenu .bar.view 5 [list showview $n] \
3824 entryconf [list -label $viewname($n)]
3825 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3826 # entryconf [list -label $viewname($n) -value $viewname($n)]
3828 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3829 $newviewopts($n,cmd) ne $viewargscmd($n)} {
3830 set viewfiles($n) $files
3831 set viewargs($n) $newargs
3832 set viewargscmd($n) $newviewopts($n,cmd)
3833 if {$curview == $n} {
3834 run reloadcommits
3838 if {$apply} return
3839 catch {destroy $top}
3842 proc delview {} {
3843 global curview viewperm hlview selectedhlview
3845 if {$curview == 0} return
3846 if {[info exists hlview] && $hlview == $curview} {
3847 set selectedhlview [mc "None"]
3848 unset hlview
3850 allviewmenus $curview delete
3851 set viewperm($curview) 0
3852 showview 0
3855 proc addviewmenu {n} {
3856 global viewname viewhlmenu
3858 .bar.view add radiobutton -label $viewname($n) \
3859 -command [list showview $n] -variable selectedview -value $n
3860 #$viewhlmenu add radiobutton -label $viewname($n) \
3861 # -command [list addvhighlight $n] -variable selectedhlview
3864 proc showview {n} {
3865 global curview cached_commitrow ordertok
3866 global displayorder parentlist rowidlist rowisopt rowfinal
3867 global colormap rowtextx nextcolor canvxmax
3868 global numcommits viewcomplete
3869 global selectedline currentid canv canvy0
3870 global treediffs
3871 global pending_select mainheadid
3872 global commitidx
3873 global selectedview
3874 global hlview selectedhlview commitinterest
3876 if {$n == $curview} return
3877 set selid {}
3878 set ymax [lindex [$canv cget -scrollregion] 3]
3879 set span [$canv yview]
3880 set ytop [expr {[lindex $span 0] * $ymax}]
3881 set ybot [expr {[lindex $span 1] * $ymax}]
3882 set yscreen [expr {($ybot - $ytop) / 2}]
3883 if {$selectedline ne {}} {
3884 set selid $currentid
3885 set y [yc $selectedline]
3886 if {$ytop < $y && $y < $ybot} {
3887 set yscreen [expr {$y - $ytop}]
3889 } elseif {[info exists pending_select]} {
3890 set selid $pending_select
3891 unset pending_select
3893 unselectline
3894 normalline
3895 catch {unset treediffs}
3896 clear_display
3897 if {[info exists hlview] && $hlview == $n} {
3898 unset hlview
3899 set selectedhlview [mc "None"]
3901 catch {unset commitinterest}
3902 catch {unset cached_commitrow}
3903 catch {unset ordertok}
3905 set curview $n
3906 set selectedview $n
3907 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3908 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3910 run refill_reflist
3911 if {![info exists viewcomplete($n)]} {
3912 getcommits $selid
3913 return
3916 set displayorder {}
3917 set parentlist {}
3918 set rowidlist {}
3919 set rowisopt {}
3920 set rowfinal {}
3921 set numcommits $commitidx($n)
3923 catch {unset colormap}
3924 catch {unset rowtextx}
3925 set nextcolor 0
3926 set canvxmax [$canv cget -width]
3927 set curview $n
3928 set row 0
3929 setcanvscroll
3930 set yf 0
3931 set row {}
3932 if {$selid ne {} && [commitinview $selid $n]} {
3933 set row [rowofcommit $selid]
3934 # try to get the selected row in the same position on the screen
3935 set ymax [lindex [$canv cget -scrollregion] 3]
3936 set ytop [expr {[yc $row] - $yscreen}]
3937 if {$ytop < 0} {
3938 set ytop 0
3940 set yf [expr {$ytop * 1.0 / $ymax}]
3942 allcanvs yview moveto $yf
3943 drawvisible
3944 if {$row ne {}} {
3945 selectline $row 0
3946 } elseif {!$viewcomplete($n)} {
3947 reset_pending_select $selid
3948 } else {
3949 reset_pending_select {}
3951 if {[commitinview $pending_select $curview]} {
3952 selectline [rowofcommit $pending_select] 1
3953 } else {
3954 set row [first_real_row]
3955 if {$row < $numcommits} {
3956 selectline $row 0
3960 if {!$viewcomplete($n)} {
3961 if {$numcommits == 0} {
3962 show_status [mc "Reading commits..."]
3964 } elseif {$numcommits == 0} {
3965 show_status [mc "No commits selected"]
3969 # Stuff relating to the highlighting facility
3971 proc ishighlighted {id} {
3972 global vhighlights fhighlights nhighlights rhighlights
3974 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3975 return $nhighlights($id)
3977 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3978 return $vhighlights($id)
3980 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3981 return $fhighlights($id)
3983 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3984 return $rhighlights($id)
3986 return 0
3989 proc bolden {row font} {
3990 global canv linehtag selectedline boldrows
3992 lappend boldrows $row
3993 $canv itemconf $linehtag($row) -font $font
3994 if {$row == $selectedline} {
3995 $canv delete secsel
3996 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3997 -outline {{}} -tags secsel \
3998 -fill [$canv cget -selectbackground]]
3999 $canv lower $t
4003 proc bolden_name {row font} {
4004 global canv2 linentag selectedline boldnamerows
4006 lappend boldnamerows $row
4007 $canv2 itemconf $linentag($row) -font $font
4008 if {$row == $selectedline} {
4009 $canv2 delete secsel
4010 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
4011 -outline {{}} -tags secsel \
4012 -fill [$canv2 cget -selectbackground]]
4013 $canv2 lower $t
4017 proc unbolden {} {
4018 global boldrows
4020 set stillbold {}
4021 foreach row $boldrows {
4022 if {![ishighlighted [commitonrow $row]]} {
4023 bolden $row mainfont
4024 } else {
4025 lappend stillbold $row
4028 set boldrows $stillbold
4031 proc addvhighlight {n} {
4032 global hlview viewcomplete curview vhl_done commitidx
4034 if {[info exists hlview]} {
4035 delvhighlight
4037 set hlview $n
4038 if {$n != $curview && ![info exists viewcomplete($n)]} {
4039 start_rev_list $n
4041 set vhl_done $commitidx($hlview)
4042 if {$vhl_done > 0} {
4043 drawvisible
4047 proc delvhighlight {} {
4048 global hlview vhighlights
4050 if {![info exists hlview]} return
4051 unset hlview
4052 catch {unset vhighlights}
4053 unbolden
4056 proc vhighlightmore {} {
4057 global hlview vhl_done commitidx vhighlights curview
4059 set max $commitidx($hlview)
4060 set vr [visiblerows]
4061 set r0 [lindex $vr 0]
4062 set r1 [lindex $vr 1]
4063 for {set i $vhl_done} {$i < $max} {incr i} {
4064 set id [commitonrow $i $hlview]
4065 if {[commitinview $id $curview]} {
4066 set row [rowofcommit $id]
4067 if {$r0 <= $row && $row <= $r1} {
4068 if {![highlighted $row]} {
4069 bolden $row mainfontbold
4071 set vhighlights($id) 1
4075 set vhl_done $max
4076 return 0
4079 proc askvhighlight {row id} {
4080 global hlview vhighlights iddrawn
4082 if {[commitinview $id $hlview]} {
4083 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
4084 bolden $row mainfontbold
4086 set vhighlights($id) 1
4087 } else {
4088 set vhighlights($id) 0
4092 proc hfiles_change {} {
4093 global highlight_files filehighlight fhighlights fh_serial
4094 global highlight_paths gdttype
4096 if {[info exists filehighlight]} {
4097 # delete previous highlights
4098 catch {close $filehighlight}
4099 unset filehighlight
4100 catch {unset fhighlights}
4101 unbolden
4102 unhighlight_filelist
4104 set highlight_paths {}
4105 after cancel do_file_hl $fh_serial
4106 incr fh_serial
4107 if {$highlight_files ne {}} {
4108 after 300 do_file_hl $fh_serial
4112 proc gdttype_change {name ix op} {
4113 global gdttype highlight_files findstring findpattern
4115 stopfinding
4116 if {$findstring ne {}} {
4117 if {$gdttype eq [mc "containing:"]} {
4118 if {$highlight_files ne {}} {
4119 set highlight_files {}
4120 hfiles_change
4122 findcom_change
4123 } else {
4124 if {$findpattern ne {}} {
4125 set findpattern {}
4126 findcom_change
4128 set highlight_files $findstring
4129 hfiles_change
4131 drawvisible
4133 # enable/disable findtype/findloc menus too
4136 proc find_change {name ix op} {
4137 global gdttype findstring highlight_files
4139 stopfinding
4140 if {$gdttype eq [mc "containing:"]} {
4141 findcom_change
4142 } else {
4143 if {$highlight_files ne $findstring} {
4144 set highlight_files $findstring
4145 hfiles_change
4148 drawvisible
4151 proc findcom_change args {
4152 global nhighlights boldnamerows
4153 global findpattern findtype findstring gdttype
4155 stopfinding
4156 # delete previous highlights, if any
4157 foreach row $boldnamerows {
4158 bolden_name $row mainfont
4160 set boldnamerows {}
4161 catch {unset nhighlights}
4162 unbolden
4163 unmarkmatches
4164 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4165 set findpattern {}
4166 } elseif {$findtype eq [mc "Regexp"]} {
4167 set findpattern $findstring
4168 } else {
4169 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4170 $findstring]
4171 set findpattern "*$e*"
4175 proc makepatterns {l} {
4176 set ret {}
4177 foreach e $l {
4178 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4179 if {[string index $ee end] eq "/"} {
4180 lappend ret "$ee*"
4181 } else {
4182 lappend ret $ee
4183 lappend ret "$ee/*"
4186 return $ret
4189 proc do_file_hl {serial} {
4190 global highlight_files filehighlight highlight_paths gdttype fhl_list
4192 if {$gdttype eq [mc "touching paths:"]} {
4193 if {[catch {set paths [shellsplit $highlight_files]}]} return
4194 set highlight_paths [makepatterns $paths]
4195 highlight_filelist
4196 set gdtargs [concat -- $paths]
4197 } elseif {$gdttype eq [mc "adding/removing string:"]} {
4198 set gdtargs [list "-S$highlight_files"]
4199 } else {
4200 # must be "containing:", i.e. we're searching commit info
4201 return
4203 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4204 set filehighlight [open $cmd r+]
4205 fconfigure $filehighlight -blocking 0
4206 filerun $filehighlight readfhighlight
4207 set fhl_list {}
4208 drawvisible
4209 flushhighlights
4212 proc flushhighlights {} {
4213 global filehighlight fhl_list
4215 if {[info exists filehighlight]} {
4216 lappend fhl_list {}
4217 puts $filehighlight ""
4218 flush $filehighlight
4222 proc askfilehighlight {row id} {
4223 global filehighlight fhighlights fhl_list
4225 lappend fhl_list $id
4226 set fhighlights($id) -1
4227 puts $filehighlight $id
4230 proc readfhighlight {} {
4231 global filehighlight fhighlights curview iddrawn
4232 global fhl_list find_dirn
4234 if {![info exists filehighlight]} {
4235 return 0
4237 set nr 0
4238 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4239 set line [string trim $line]
4240 set i [lsearch -exact $fhl_list $line]
4241 if {$i < 0} continue
4242 for {set j 0} {$j < $i} {incr j} {
4243 set id [lindex $fhl_list $j]
4244 set fhighlights($id) 0
4246 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4247 if {$line eq {}} continue
4248 if {![commitinview $line $curview]} continue
4249 set row [rowofcommit $line]
4250 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4251 bolden $row mainfontbold
4253 set fhighlights($line) 1
4255 if {[eof $filehighlight]} {
4256 # strange...
4257 puts "oops, git diff-tree died"
4258 catch {close $filehighlight}
4259 unset filehighlight
4260 return 0
4262 if {[info exists find_dirn]} {
4263 run findmore
4265 return 1
4268 proc doesmatch {f} {
4269 global findtype findpattern
4271 if {$findtype eq [mc "Regexp"]} {
4272 return [regexp $findpattern $f]
4273 } elseif {$findtype eq [mc "IgnCase"]} {
4274 return [string match -nocase $findpattern $f]
4275 } else {
4276 return [string match $findpattern $f]
4280 proc askfindhighlight {row id} {
4281 global nhighlights commitinfo iddrawn
4282 global findloc
4283 global markingmatches
4285 if {![info exists commitinfo($id)]} {
4286 getcommit $id
4288 set info $commitinfo($id)
4289 set isbold 0
4290 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4291 foreach f $info ty $fldtypes {
4292 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4293 [doesmatch $f]} {
4294 if {$ty eq [mc "Author"]} {
4295 set isbold 2
4296 break
4298 set isbold 1
4301 if {$isbold && [info exists iddrawn($id)]} {
4302 if {![ishighlighted $id]} {
4303 bolden $row mainfontbold
4304 if {$isbold > 1} {
4305 bolden_name $row mainfontbold
4308 if {$markingmatches} {
4309 markrowmatches $row $id
4312 set nhighlights($id) $isbold
4315 proc markrowmatches {row id} {
4316 global canv canv2 linehtag linentag commitinfo findloc
4318 set headline [lindex $commitinfo($id) 0]
4319 set author [lindex $commitinfo($id) 1]
4320 $canv delete match$row
4321 $canv2 delete match$row
4322 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4323 set m [findmatches $headline]
4324 if {$m ne {}} {
4325 markmatches $canv $row $headline $linehtag($row) $m \
4326 [$canv itemcget $linehtag($row) -font] $row
4329 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4330 set m [findmatches $author]
4331 if {$m ne {}} {
4332 markmatches $canv2 $row $author $linentag($row) $m \
4333 [$canv2 itemcget $linentag($row) -font] $row
4338 proc vrel_change {name ix op} {
4339 global highlight_related
4341 rhighlight_none
4342 if {$highlight_related ne [mc "None"]} {
4343 run drawvisible
4347 # prepare for testing whether commits are descendents or ancestors of a
4348 proc rhighlight_sel {a} {
4349 global descendent desc_todo ancestor anc_todo
4350 global highlight_related
4352 catch {unset descendent}
4353 set desc_todo [list $a]
4354 catch {unset ancestor}
4355 set anc_todo [list $a]
4356 if {$highlight_related ne [mc "None"]} {
4357 rhighlight_none
4358 run drawvisible
4362 proc rhighlight_none {} {
4363 global rhighlights
4365 catch {unset rhighlights}
4366 unbolden
4369 proc is_descendent {a} {
4370 global curview children descendent desc_todo
4372 set v $curview
4373 set la [rowofcommit $a]
4374 set todo $desc_todo
4375 set leftover {}
4376 set done 0
4377 for {set i 0} {$i < [llength $todo]} {incr i} {
4378 set do [lindex $todo $i]
4379 if {[rowofcommit $do] < $la} {
4380 lappend leftover $do
4381 continue
4383 foreach nk $children($v,$do) {
4384 if {![info exists descendent($nk)]} {
4385 set descendent($nk) 1
4386 lappend todo $nk
4387 if {$nk eq $a} {
4388 set done 1
4392 if {$done} {
4393 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4394 return
4397 set descendent($a) 0
4398 set desc_todo $leftover
4401 proc is_ancestor {a} {
4402 global curview parents ancestor anc_todo
4404 set v $curview
4405 set la [rowofcommit $a]
4406 set todo $anc_todo
4407 set leftover {}
4408 set done 0
4409 for {set i 0} {$i < [llength $todo]} {incr i} {
4410 set do [lindex $todo $i]
4411 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4412 lappend leftover $do
4413 continue
4415 foreach np $parents($v,$do) {
4416 if {![info exists ancestor($np)]} {
4417 set ancestor($np) 1
4418 lappend todo $np
4419 if {$np eq $a} {
4420 set done 1
4424 if {$done} {
4425 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4426 return
4429 set ancestor($a) 0
4430 set anc_todo $leftover
4433 proc askrelhighlight {row id} {
4434 global descendent highlight_related iddrawn rhighlights
4435 global selectedline ancestor
4437 if {$selectedline eq {}} return
4438 set isbold 0
4439 if {$highlight_related eq [mc "Descendant"] ||
4440 $highlight_related eq [mc "Not descendant"]} {
4441 if {![info exists descendent($id)]} {
4442 is_descendent $id
4444 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4445 set isbold 1
4447 } elseif {$highlight_related eq [mc "Ancestor"] ||
4448 $highlight_related eq [mc "Not ancestor"]} {
4449 if {![info exists ancestor($id)]} {
4450 is_ancestor $id
4452 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4453 set isbold 1
4456 if {[info exists iddrawn($id)]} {
4457 if {$isbold && ![ishighlighted $id]} {
4458 bolden $row mainfontbold
4461 set rhighlights($id) $isbold
4464 # Graph layout functions
4466 proc shortids {ids} {
4467 set res {}
4468 foreach id $ids {
4469 if {[llength $id] > 1} {
4470 lappend res [shortids $id]
4471 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4472 lappend res [string range $id 0 7]
4473 } else {
4474 lappend res $id
4477 return $res
4480 proc ntimes {n o} {
4481 set ret {}
4482 set o [list $o]
4483 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4484 if {($n & $mask) != 0} {
4485 set ret [concat $ret $o]
4487 set o [concat $o $o]
4489 return $ret
4492 proc ordertoken {id} {
4493 global ordertok curview varcid varcstart varctok curview parents children
4494 global nullid nullid2
4496 if {[info exists ordertok($id)]} {
4497 return $ordertok($id)
4499 set origid $id
4500 set todo {}
4501 while {1} {
4502 if {[info exists varcid($curview,$id)]} {
4503 set a $varcid($curview,$id)
4504 set p [lindex $varcstart($curview) $a]
4505 } else {
4506 set p [lindex $children($curview,$id) 0]
4508 if {[info exists ordertok($p)]} {
4509 set tok $ordertok($p)
4510 break
4512 set id [first_real_child $curview,$p]
4513 if {$id eq {}} {
4514 # it's a root
4515 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4516 break
4518 if {[llength $parents($curview,$id)] == 1} {
4519 lappend todo [list $p {}]
4520 } else {
4521 set j [lsearch -exact $parents($curview,$id) $p]
4522 if {$j < 0} {
4523 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4525 lappend todo [list $p [strrep $j]]
4528 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4529 set p [lindex $todo $i 0]
4530 append tok [lindex $todo $i 1]
4531 set ordertok($p) $tok
4533 set ordertok($origid) $tok
4534 return $tok
4537 # Work out where id should go in idlist so that order-token
4538 # values increase from left to right
4539 proc idcol {idlist id {i 0}} {
4540 set t [ordertoken $id]
4541 if {$i < 0} {
4542 set i 0
4544 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4545 if {$i > [llength $idlist]} {
4546 set i [llength $idlist]
4548 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4549 incr i
4550 } else {
4551 if {$t > [ordertoken [lindex $idlist $i]]} {
4552 while {[incr i] < [llength $idlist] &&
4553 $t >= [ordertoken [lindex $idlist $i]]} {}
4556 return $i
4559 proc initlayout {} {
4560 global rowidlist rowisopt rowfinal displayorder parentlist
4561 global numcommits canvxmax canv
4562 global nextcolor
4563 global colormap rowtextx
4565 set numcommits 0
4566 set displayorder {}
4567 set parentlist {}
4568 set nextcolor 0
4569 set rowidlist {}
4570 set rowisopt {}
4571 set rowfinal {}
4572 set canvxmax [$canv cget -width]
4573 catch {unset colormap}
4574 catch {unset rowtextx}
4575 setcanvscroll
4578 proc setcanvscroll {} {
4579 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4580 global lastscrollset lastscrollrows
4582 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4583 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4584 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4585 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4586 set lastscrollset [clock clicks -milliseconds]
4587 set lastscrollrows $numcommits
4590 proc visiblerows {} {
4591 global canv numcommits linespc
4593 set ymax [lindex [$canv cget -scrollregion] 3]
4594 if {$ymax eq {} || $ymax == 0} return
4595 set f [$canv yview]
4596 set y0 [expr {int([lindex $f 0] * $ymax)}]
4597 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4598 if {$r0 < 0} {
4599 set r0 0
4601 set y1 [expr {int([lindex $f 1] * $ymax)}]
4602 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4603 if {$r1 >= $numcommits} {
4604 set r1 [expr {$numcommits - 1}]
4606 return [list $r0 $r1]
4609 proc layoutmore {} {
4610 global commitidx viewcomplete curview
4611 global numcommits pending_select curview
4612 global lastscrollset lastscrollrows
4614 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4615 [clock clicks -milliseconds] - $lastscrollset > 500} {
4616 setcanvscroll
4618 if {[info exists pending_select] &&
4619 [commitinview $pending_select $curview]} {
4620 update
4621 selectline [rowofcommit $pending_select] 1
4623 drawvisible
4626 proc doshowlocalchanges {} {
4627 global curview mainheadid
4629 if {$mainheadid eq {}} return
4630 if {[commitinview $mainheadid $curview]} {
4631 dodiffindex
4632 } else {
4633 interestedin $mainheadid dodiffindex
4637 proc dohidelocalchanges {} {
4638 global nullid nullid2 lserial curview
4640 if {[commitinview $nullid $curview]} {
4641 removefakerow $nullid
4643 if {[commitinview $nullid2 $curview]} {
4644 removefakerow $nullid2
4646 incr lserial
4649 # spawn off a process to do git diff-index --cached HEAD
4650 proc dodiffindex {} {
4651 global lserial showlocalchanges
4652 global isworktree
4654 if {!$showlocalchanges || !$isworktree} return
4655 incr lserial
4656 set fd [open "|git diff-index --cached HEAD" r]
4657 fconfigure $fd -blocking 0
4658 set i [reg_instance $fd]
4659 filerun $fd [list readdiffindex $fd $lserial $i]
4662 proc readdiffindex {fd serial inst} {
4663 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4665 set isdiff 1
4666 if {[gets $fd line] < 0} {
4667 if {![eof $fd]} {
4668 return 1
4670 set isdiff 0
4672 # we only need to see one line and we don't really care what it says...
4673 stop_instance $inst
4675 if {$serial != $lserial} {
4676 return 0
4679 # now see if there are any local changes not checked in to the index
4680 set fd [open "|git diff-files" r]
4681 fconfigure $fd -blocking 0
4682 set i [reg_instance $fd]
4683 filerun $fd [list readdifffiles $fd $serial $i]
4685 if {$isdiff && ![commitinview $nullid2 $curview]} {
4686 # add the line for the changes in the index to the graph
4687 set hl [mc "Local changes checked in to index but not committed"]
4688 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4689 set commitdata($nullid2) "\n $hl\n"
4690 if {[commitinview $nullid $curview]} {
4691 removefakerow $nullid
4693 insertfakerow $nullid2 $mainheadid
4694 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4695 removefakerow $nullid2
4697 return 0
4700 proc readdifffiles {fd serial inst} {
4701 global mainheadid nullid nullid2 curview
4702 global commitinfo commitdata lserial
4704 set isdiff 1
4705 if {[gets $fd line] < 0} {
4706 if {![eof $fd]} {
4707 return 1
4709 set isdiff 0
4711 # we only need to see one line and we don't really care what it says...
4712 stop_instance $inst
4714 if {$serial != $lserial} {
4715 return 0
4718 if {$isdiff && ![commitinview $nullid $curview]} {
4719 # add the line for the local diff to the graph
4720 set hl [mc "Local uncommitted changes, not checked in to index"]
4721 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4722 set commitdata($nullid) "\n $hl\n"
4723 if {[commitinview $nullid2 $curview]} {
4724 set p $nullid2
4725 } else {
4726 set p $mainheadid
4728 insertfakerow $nullid $p
4729 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4730 removefakerow $nullid
4732 return 0
4735 proc nextuse {id row} {
4736 global curview children
4738 if {[info exists children($curview,$id)]} {
4739 foreach kid $children($curview,$id) {
4740 if {![commitinview $kid $curview]} {
4741 return -1
4743 if {[rowofcommit $kid] > $row} {
4744 return [rowofcommit $kid]
4748 if {[commitinview $id $curview]} {
4749 return [rowofcommit $id]
4751 return -1
4754 proc prevuse {id row} {
4755 global curview children
4757 set ret -1
4758 if {[info exists children($curview,$id)]} {
4759 foreach kid $children($curview,$id) {
4760 if {![commitinview $kid $curview]} break
4761 if {[rowofcommit $kid] < $row} {
4762 set ret [rowofcommit $kid]
4766 return $ret
4769 proc make_idlist {row} {
4770 global displayorder parentlist uparrowlen downarrowlen mingaplen
4771 global commitidx curview children
4773 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4774 if {$r < 0} {
4775 set r 0
4777 set ra [expr {$row - $downarrowlen}]
4778 if {$ra < 0} {
4779 set ra 0
4781 set rb [expr {$row + $uparrowlen}]
4782 if {$rb > $commitidx($curview)} {
4783 set rb $commitidx($curview)
4785 make_disporder $r [expr {$rb + 1}]
4786 set ids {}
4787 for {} {$r < $ra} {incr r} {
4788 set nextid [lindex $displayorder [expr {$r + 1}]]
4789 foreach p [lindex $parentlist $r] {
4790 if {$p eq $nextid} continue
4791 set rn [nextuse $p $r]
4792 if {$rn >= $row &&
4793 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4794 lappend ids [list [ordertoken $p] $p]
4798 for {} {$r < $row} {incr r} {
4799 set nextid [lindex $displayorder [expr {$r + 1}]]
4800 foreach p [lindex $parentlist $r] {
4801 if {$p eq $nextid} continue
4802 set rn [nextuse $p $r]
4803 if {$rn < 0 || $rn >= $row} {
4804 lappend ids [list [ordertoken $p] $p]
4808 set id [lindex $displayorder $row]
4809 lappend ids [list [ordertoken $id] $id]
4810 while {$r < $rb} {
4811 foreach p [lindex $parentlist $r] {
4812 set firstkid [lindex $children($curview,$p) 0]
4813 if {[rowofcommit $firstkid] < $row} {
4814 lappend ids [list [ordertoken $p] $p]
4817 incr r
4818 set id [lindex $displayorder $r]
4819 if {$id ne {}} {
4820 set firstkid [lindex $children($curview,$id) 0]
4821 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4822 lappend ids [list [ordertoken $id] $id]
4826 set idlist {}
4827 foreach idx [lsort -unique $ids] {
4828 lappend idlist [lindex $idx 1]
4830 return $idlist
4833 proc rowsequal {a b} {
4834 while {[set i [lsearch -exact $a {}]] >= 0} {
4835 set a [lreplace $a $i $i]
4837 while {[set i [lsearch -exact $b {}]] >= 0} {
4838 set b [lreplace $b $i $i]
4840 return [expr {$a eq $b}]
4843 proc makeupline {id row rend col} {
4844 global rowidlist uparrowlen downarrowlen mingaplen
4846 for {set r $rend} {1} {set r $rstart} {
4847 set rstart [prevuse $id $r]
4848 if {$rstart < 0} return
4849 if {$rstart < $row} break
4851 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4852 set rstart [expr {$rend - $uparrowlen - 1}]
4854 for {set r $rstart} {[incr r] <= $row} {} {
4855 set idlist [lindex $rowidlist $r]
4856 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4857 set col [idcol $idlist $id $col]
4858 lset rowidlist $r [linsert $idlist $col $id]
4859 changedrow $r
4864 proc layoutrows {row endrow} {
4865 global rowidlist rowisopt rowfinal displayorder
4866 global uparrowlen downarrowlen maxwidth mingaplen
4867 global children parentlist
4868 global commitidx viewcomplete curview
4870 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4871 set idlist {}
4872 if {$row > 0} {
4873 set rm1 [expr {$row - 1}]
4874 foreach id [lindex $rowidlist $rm1] {
4875 if {$id ne {}} {
4876 lappend idlist $id
4879 set final [lindex $rowfinal $rm1]
4881 for {} {$row < $endrow} {incr row} {
4882 set rm1 [expr {$row - 1}]
4883 if {$rm1 < 0 || $idlist eq {}} {
4884 set idlist [make_idlist $row]
4885 set final 1
4886 } else {
4887 set id [lindex $displayorder $rm1]
4888 set col [lsearch -exact $idlist $id]
4889 set idlist [lreplace $idlist $col $col]
4890 foreach p [lindex $parentlist $rm1] {
4891 if {[lsearch -exact $idlist $p] < 0} {
4892 set col [idcol $idlist $p $col]
4893 set idlist [linsert $idlist $col $p]
4894 # if not the first child, we have to insert a line going up
4895 if {$id ne [lindex $children($curview,$p) 0]} {
4896 makeupline $p $rm1 $row $col
4900 set id [lindex $displayorder $row]
4901 if {$row > $downarrowlen} {
4902 set termrow [expr {$row - $downarrowlen - 1}]
4903 foreach p [lindex $parentlist $termrow] {
4904 set i [lsearch -exact $idlist $p]
4905 if {$i < 0} continue
4906 set nr [nextuse $p $termrow]
4907 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4908 set idlist [lreplace $idlist $i $i]
4912 set col [lsearch -exact $idlist $id]
4913 if {$col < 0} {
4914 set col [idcol $idlist $id]
4915 set idlist [linsert $idlist $col $id]
4916 if {$children($curview,$id) ne {}} {
4917 makeupline $id $rm1 $row $col
4920 set r [expr {$row + $uparrowlen - 1}]
4921 if {$r < $commitidx($curview)} {
4922 set x $col
4923 foreach p [lindex $parentlist $r] {
4924 if {[lsearch -exact $idlist $p] >= 0} continue
4925 set fk [lindex $children($curview,$p) 0]
4926 if {[rowofcommit $fk] < $row} {
4927 set x [idcol $idlist $p $x]
4928 set idlist [linsert $idlist $x $p]
4931 if {[incr r] < $commitidx($curview)} {
4932 set p [lindex $displayorder $r]
4933 if {[lsearch -exact $idlist $p] < 0} {
4934 set fk [lindex $children($curview,$p) 0]
4935 if {$fk ne {} && [rowofcommit $fk] < $row} {
4936 set x [idcol $idlist $p $x]
4937 set idlist [linsert $idlist $x $p]
4943 if {$final && !$viewcomplete($curview) &&
4944 $row + $uparrowlen + $mingaplen + $downarrowlen
4945 >= $commitidx($curview)} {
4946 set final 0
4948 set l [llength $rowidlist]
4949 if {$row == $l} {
4950 lappend rowidlist $idlist
4951 lappend rowisopt 0
4952 lappend rowfinal $final
4953 } elseif {$row < $l} {
4954 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4955 lset rowidlist $row $idlist
4956 changedrow $row
4958 lset rowfinal $row $final
4959 } else {
4960 set pad [ntimes [expr {$row - $l}] {}]
4961 set rowidlist [concat $rowidlist $pad]
4962 lappend rowidlist $idlist
4963 set rowfinal [concat $rowfinal $pad]
4964 lappend rowfinal $final
4965 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4968 return $row
4971 proc changedrow {row} {
4972 global displayorder iddrawn rowisopt need_redisplay
4974 set l [llength $rowisopt]
4975 if {$row < $l} {
4976 lset rowisopt $row 0
4977 if {$row + 1 < $l} {
4978 lset rowisopt [expr {$row + 1}] 0
4979 if {$row + 2 < $l} {
4980 lset rowisopt [expr {$row + 2}] 0
4984 set id [lindex $displayorder $row]
4985 if {[info exists iddrawn($id)]} {
4986 set need_redisplay 1
4990 proc insert_pad {row col npad} {
4991 global rowidlist
4993 set pad [ntimes $npad {}]
4994 set idlist [lindex $rowidlist $row]
4995 set bef [lrange $idlist 0 [expr {$col - 1}]]
4996 set aft [lrange $idlist $col end]
4997 set i [lsearch -exact $aft {}]
4998 if {$i > 0} {
4999 set aft [lreplace $aft $i $i]
5001 lset rowidlist $row [concat $bef $pad $aft]
5002 changedrow $row
5005 proc optimize_rows {row col endrow} {
5006 global rowidlist rowisopt displayorder curview children
5008 if {$row < 1} {
5009 set row 1
5011 for {} {$row < $endrow} {incr row; set col 0} {
5012 if {[lindex $rowisopt $row]} continue
5013 set haspad 0
5014 set y0 [expr {$row - 1}]
5015 set ym [expr {$row - 2}]
5016 set idlist [lindex $rowidlist $row]
5017 set previdlist [lindex $rowidlist $y0]
5018 if {$idlist eq {} || $previdlist eq {}} continue
5019 if {$ym >= 0} {
5020 set pprevidlist [lindex $rowidlist $ym]
5021 if {$pprevidlist eq {}} continue
5022 } else {
5023 set pprevidlist {}
5025 set x0 -1
5026 set xm -1
5027 for {} {$col < [llength $idlist]} {incr col} {
5028 set id [lindex $idlist $col]
5029 if {[lindex $previdlist $col] eq $id} continue
5030 if {$id eq {}} {
5031 set haspad 1
5032 continue
5034 set x0 [lsearch -exact $previdlist $id]
5035 if {$x0 < 0} continue
5036 set z [expr {$x0 - $col}]
5037 set isarrow 0
5038 set z0 {}
5039 if {$ym >= 0} {
5040 set xm [lsearch -exact $pprevidlist $id]
5041 if {$xm >= 0} {
5042 set z0 [expr {$xm - $x0}]
5045 if {$z0 eq {}} {
5046 # if row y0 is the first child of $id then it's not an arrow
5047 if {[lindex $children($curview,$id) 0] ne
5048 [lindex $displayorder $y0]} {
5049 set isarrow 1
5052 if {!$isarrow && $id ne [lindex $displayorder $row] &&
5053 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
5054 set isarrow 1
5056 # Looking at lines from this row to the previous row,
5057 # make them go straight up if they end in an arrow on
5058 # the previous row; otherwise make them go straight up
5059 # or at 45 degrees.
5060 if {$z < -1 || ($z < 0 && $isarrow)} {
5061 # Line currently goes left too much;
5062 # insert pads in the previous row, then optimize it
5063 set npad [expr {-1 - $z + $isarrow}]
5064 insert_pad $y0 $x0 $npad
5065 if {$y0 > 0} {
5066 optimize_rows $y0 $x0 $row
5068 set previdlist [lindex $rowidlist $y0]
5069 set x0 [lsearch -exact $previdlist $id]
5070 set z [expr {$x0 - $col}]
5071 if {$z0 ne {}} {
5072 set pprevidlist [lindex $rowidlist $ym]
5073 set xm [lsearch -exact $pprevidlist $id]
5074 set z0 [expr {$xm - $x0}]
5076 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
5077 # Line currently goes right too much;
5078 # insert pads in this line
5079 set npad [expr {$z - 1 + $isarrow}]
5080 insert_pad $row $col $npad
5081 set idlist [lindex $rowidlist $row]
5082 incr col $npad
5083 set z [expr {$x0 - $col}]
5084 set haspad 1
5086 if {$z0 eq {} && !$isarrow && $ym >= 0} {
5087 # this line links to its first child on row $row-2
5088 set id [lindex $displayorder $ym]
5089 set xc [lsearch -exact $pprevidlist $id]
5090 if {$xc >= 0} {
5091 set z0 [expr {$xc - $x0}]
5094 # avoid lines jigging left then immediately right
5095 if {$z0 ne {} && $z < 0 && $z0 > 0} {
5096 insert_pad $y0 $x0 1
5097 incr x0
5098 optimize_rows $y0 $x0 $row
5099 set previdlist [lindex $rowidlist $y0]
5102 if {!$haspad} {
5103 # Find the first column that doesn't have a line going right
5104 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5105 set id [lindex $idlist $col]
5106 if {$id eq {}} break
5107 set x0 [lsearch -exact $previdlist $id]
5108 if {$x0 < 0} {
5109 # check if this is the link to the first child
5110 set kid [lindex $displayorder $y0]
5111 if {[lindex $children($curview,$id) 0] eq $kid} {
5112 # it is, work out offset to child
5113 set x0 [lsearch -exact $previdlist $kid]
5116 if {$x0 <= $col} break
5118 # Insert a pad at that column as long as it has a line and
5119 # isn't the last column
5120 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5121 set idlist [linsert $idlist $col {}]
5122 lset rowidlist $row $idlist
5123 changedrow $row
5129 proc xc {row col} {
5130 global canvx0 linespc
5131 return [expr {$canvx0 + $col * $linespc}]
5134 proc yc {row} {
5135 global canvy0 linespc
5136 return [expr {$canvy0 + $row * $linespc}]
5139 proc linewidth {id} {
5140 global thickerline lthickness
5142 set wid $lthickness
5143 if {[info exists thickerline] && $id eq $thickerline} {
5144 set wid [expr {2 * $lthickness}]
5146 return $wid
5149 proc rowranges {id} {
5150 global curview children uparrowlen downarrowlen
5151 global rowidlist
5153 set kids $children($curview,$id)
5154 if {$kids eq {}} {
5155 return {}
5157 set ret {}
5158 lappend kids $id
5159 foreach child $kids {
5160 if {![commitinview $child $curview]} break
5161 set row [rowofcommit $child]
5162 if {![info exists prev]} {
5163 lappend ret [expr {$row + 1}]
5164 } else {
5165 if {$row <= $prevrow} {
5166 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5168 # see if the line extends the whole way from prevrow to row
5169 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5170 [lsearch -exact [lindex $rowidlist \
5171 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5172 # it doesn't, see where it ends
5173 set r [expr {$prevrow + $downarrowlen}]
5174 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5175 while {[incr r -1] > $prevrow &&
5176 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5177 } else {
5178 while {[incr r] <= $row &&
5179 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5180 incr r -1
5182 lappend ret $r
5183 # see where it starts up again
5184 set r [expr {$row - $uparrowlen}]
5185 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5186 while {[incr r] < $row &&
5187 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5188 } else {
5189 while {[incr r -1] >= $prevrow &&
5190 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5191 incr r
5193 lappend ret $r
5196 if {$child eq $id} {
5197 lappend ret $row
5199 set prev $child
5200 set prevrow $row
5202 return $ret
5205 proc drawlineseg {id row endrow arrowlow} {
5206 global rowidlist displayorder iddrawn linesegs
5207 global canv colormap linespc curview maxlinelen parentlist
5209 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5210 set le [expr {$row + 1}]
5211 set arrowhigh 1
5212 while {1} {
5213 set c [lsearch -exact [lindex $rowidlist $le] $id]
5214 if {$c < 0} {
5215 incr le -1
5216 break
5218 lappend cols $c
5219 set x [lindex $displayorder $le]
5220 if {$x eq $id} {
5221 set arrowhigh 0
5222 break
5224 if {[info exists iddrawn($x)] || $le == $endrow} {
5225 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5226 if {$c >= 0} {
5227 lappend cols $c
5228 set arrowhigh 0
5230 break
5232 incr le
5234 if {$le <= $row} {
5235 return $row
5238 set lines {}
5239 set i 0
5240 set joinhigh 0
5241 if {[info exists linesegs($id)]} {
5242 set lines $linesegs($id)
5243 foreach li $lines {
5244 set r0 [lindex $li 0]
5245 if {$r0 > $row} {
5246 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5247 set joinhigh 1
5249 break
5251 incr i
5254 set joinlow 0
5255 if {$i > 0} {
5256 set li [lindex $lines [expr {$i-1}]]
5257 set r1 [lindex $li 1]
5258 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5259 set joinlow 1
5263 set x [lindex $cols [expr {$le - $row}]]
5264 set xp [lindex $cols [expr {$le - 1 - $row}]]
5265 set dir [expr {$xp - $x}]
5266 if {$joinhigh} {
5267 set ith [lindex $lines $i 2]
5268 set coords [$canv coords $ith]
5269 set ah [$canv itemcget $ith -arrow]
5270 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5271 set x2 [lindex $cols [expr {$le + 1 - $row}]]
5272 if {$x2 ne {} && $x - $x2 == $dir} {
5273 set coords [lrange $coords 0 end-2]
5275 } else {
5276 set coords [list [xc $le $x] [yc $le]]
5278 if {$joinlow} {
5279 set itl [lindex $lines [expr {$i-1}] 2]
5280 set al [$canv itemcget $itl -arrow]
5281 set arrowlow [expr {$al eq "last" || $al eq "both"}]
5282 } elseif {$arrowlow} {
5283 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5284 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5285 set arrowlow 0
5288 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5289 for {set y $le} {[incr y -1] > $row} {} {
5290 set x $xp
5291 set xp [lindex $cols [expr {$y - 1 - $row}]]
5292 set ndir [expr {$xp - $x}]
5293 if {$dir != $ndir || $xp < 0} {
5294 lappend coords [xc $y $x] [yc $y]
5296 set dir $ndir
5298 if {!$joinlow} {
5299 if {$xp < 0} {
5300 # join parent line to first child
5301 set ch [lindex $displayorder $row]
5302 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5303 if {$xc < 0} {
5304 puts "oops: drawlineseg: child $ch not on row $row"
5305 } elseif {$xc != $x} {
5306 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5307 set d [expr {int(0.5 * $linespc)}]
5308 set x1 [xc $row $x]
5309 if {$xc < $x} {
5310 set x2 [expr {$x1 - $d}]
5311 } else {
5312 set x2 [expr {$x1 + $d}]
5314 set y2 [yc $row]
5315 set y1 [expr {$y2 + $d}]
5316 lappend coords $x1 $y1 $x2 $y2
5317 } elseif {$xc < $x - 1} {
5318 lappend coords [xc $row [expr {$x-1}]] [yc $row]
5319 } elseif {$xc > $x + 1} {
5320 lappend coords [xc $row [expr {$x+1}]] [yc $row]
5322 set x $xc
5324 lappend coords [xc $row $x] [yc $row]
5325 } else {
5326 set xn [xc $row $xp]
5327 set yn [yc $row]
5328 lappend coords $xn $yn
5330 if {!$joinhigh} {
5331 assigncolor $id
5332 set t [$canv create line $coords -width [linewidth $id] \
5333 -fill $colormap($id) -tags lines.$id -arrow $arrow]
5334 $canv lower $t
5335 bindline $t $id
5336 set lines [linsert $lines $i [list $row $le $t]]
5337 } else {
5338 $canv coords $ith $coords
5339 if {$arrow ne $ah} {
5340 $canv itemconf $ith -arrow $arrow
5342 lset lines $i 0 $row
5344 } else {
5345 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5346 set ndir [expr {$xo - $xp}]
5347 set clow [$canv coords $itl]
5348 if {$dir == $ndir} {
5349 set clow [lrange $clow 2 end]
5351 set coords [concat $coords $clow]
5352 if {!$joinhigh} {
5353 lset lines [expr {$i-1}] 1 $le
5354 } else {
5355 # coalesce two pieces
5356 $canv delete $ith
5357 set b [lindex $lines [expr {$i-1}] 0]
5358 set e [lindex $lines $i 1]
5359 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5361 $canv coords $itl $coords
5362 if {$arrow ne $al} {
5363 $canv itemconf $itl -arrow $arrow
5367 set linesegs($id) $lines
5368 return $le
5371 proc drawparentlinks {id row} {
5372 global rowidlist canv colormap curview parentlist
5373 global idpos linespc
5375 set rowids [lindex $rowidlist $row]
5376 set col [lsearch -exact $rowids $id]
5377 if {$col < 0} return
5378 set olds [lindex $parentlist $row]
5379 set row2 [expr {$row + 1}]
5380 set x [xc $row $col]
5381 set y [yc $row]
5382 set y2 [yc $row2]
5383 set d [expr {int(0.5 * $linespc)}]
5384 set ymid [expr {$y + $d}]
5385 set ids [lindex $rowidlist $row2]
5386 # rmx = right-most X coord used
5387 set rmx 0
5388 foreach p $olds {
5389 set i [lsearch -exact $ids $p]
5390 if {$i < 0} {
5391 puts "oops, parent $p of $id not in list"
5392 continue
5394 set x2 [xc $row2 $i]
5395 if {$x2 > $rmx} {
5396 set rmx $x2
5398 set j [lsearch -exact $rowids $p]
5399 if {$j < 0} {
5400 # drawlineseg will do this one for us
5401 continue
5403 assigncolor $p
5404 # should handle duplicated parents here...
5405 set coords [list $x $y]
5406 if {$i != $col} {
5407 # if attaching to a vertical segment, draw a smaller
5408 # slant for visual distinctness
5409 if {$i == $j} {
5410 if {$i < $col} {
5411 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5412 } else {
5413 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5415 } elseif {$i < $col && $i < $j} {
5416 # segment slants towards us already
5417 lappend coords [xc $row $j] $y
5418 } else {
5419 if {$i < $col - 1} {
5420 lappend coords [expr {$x2 + $linespc}] $y
5421 } elseif {$i > $col + 1} {
5422 lappend coords [expr {$x2 - $linespc}] $y
5424 lappend coords $x2 $y2
5426 } else {
5427 lappend coords $x2 $y2
5429 set t [$canv create line $coords -width [linewidth $p] \
5430 -fill $colormap($p) -tags lines.$p]
5431 $canv lower $t
5432 bindline $t $p
5434 if {$rmx > [lindex $idpos($id) 1]} {
5435 lset idpos($id) 1 $rmx
5436 redrawtags $id
5440 proc drawlines {id} {
5441 global canv
5443 $canv itemconf lines.$id -width [linewidth $id]
5446 proc drawcmittext {id row col} {
5447 global linespc canv canv2 canv3 fgcolor curview
5448 global cmitlisted commitinfo rowidlist parentlist
5449 global rowtextx idpos idtags idheads idotherrefs
5450 global linehtag linentag linedtag selectedline
5451 global canvxmax boldrows boldnamerows fgcolor
5452 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5454 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5455 set listed $cmitlisted($curview,$id)
5456 if {$id eq $nullid} {
5457 set ofill red
5458 } elseif {$id eq $nullid2} {
5459 set ofill green
5460 } elseif {$id eq $mainheadid} {
5461 set ofill yellow
5462 } else {
5463 set ofill [lindex $circlecolors $listed]
5465 set x [xc $row $col]
5466 set y [yc $row]
5467 set orad [expr {$linespc / 3}]
5468 if {$listed <= 2} {
5469 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5470 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5471 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5472 } elseif {$listed == 3} {
5473 # triangle pointing left for left-side commits
5474 set t [$canv create polygon \
5475 [expr {$x - $orad}] $y \
5476 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5477 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5478 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5479 } else {
5480 # triangle pointing right for right-side commits
5481 set t [$canv create polygon \
5482 [expr {$x + $orad - 1}] $y \
5483 [expr {$x - $orad}] [expr {$y - $orad}] \
5484 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5485 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5487 set circleitem($row) $t
5488 $canv raise $t
5489 $canv bind $t <1> {selcanvline {} %x %y}
5490 set rmx [llength [lindex $rowidlist $row]]
5491 set olds [lindex $parentlist $row]
5492 if {$olds ne {}} {
5493 set nextids [lindex $rowidlist [expr {$row + 1}]]
5494 foreach p $olds {
5495 set i [lsearch -exact $nextids $p]
5496 if {$i > $rmx} {
5497 set rmx $i
5501 set xt [xc $row $rmx]
5502 set rowtextx($row) $xt
5503 set idpos($id) [list $x $xt $y]
5504 if {[info exists idtags($id)] || [info exists idheads($id)]
5505 || [info exists idotherrefs($id)]} {
5506 set xt [drawtags $id $x $xt $y]
5508 set headline [lindex $commitinfo($id) 0]
5509 set name [lindex $commitinfo($id) 1]
5510 set date [lindex $commitinfo($id) 2]
5511 set date [formatdate $date]
5512 set font mainfont
5513 set nfont mainfont
5514 set isbold [ishighlighted $id]
5515 if {$isbold > 0} {
5516 lappend boldrows $row
5517 set font mainfontbold
5518 if {$isbold > 1} {
5519 lappend boldnamerows $row
5520 set nfont mainfontbold
5523 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
5524 -text $headline -font $font -tags text]
5525 $canv bind $linehtag($row) $ctxbut "rowmenu %X %Y $id"
5526 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5527 -text $name -font $nfont -tags text]
5528 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5529 -text $date -font mainfont -tags text]
5530 if {$selectedline == $row} {
5531 make_secsel $row
5533 set xr [expr {$xt + [font measure $font $headline]}]
5534 if {$xr > $canvxmax} {
5535 set canvxmax $xr
5536 setcanvscroll
5540 proc drawcmitrow {row} {
5541 global displayorder rowidlist nrows_drawn
5542 global iddrawn markingmatches
5543 global commitinfo numcommits
5544 global filehighlight fhighlights findpattern nhighlights
5545 global hlview vhighlights
5546 global highlight_related rhighlights
5548 if {$row >= $numcommits} return
5550 set id [lindex $displayorder $row]
5551 if {[info exists hlview] && ![info exists vhighlights($id)]} {
5552 askvhighlight $row $id
5554 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5555 askfilehighlight $row $id
5557 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5558 askfindhighlight $row $id
5560 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5561 askrelhighlight $row $id
5563 if {![info exists iddrawn($id)]} {
5564 set col [lsearch -exact [lindex $rowidlist $row] $id]
5565 if {$col < 0} {
5566 puts "oops, row $row id $id not in list"
5567 return
5569 if {![info exists commitinfo($id)]} {
5570 getcommit $id
5572 assigncolor $id
5573 drawcmittext $id $row $col
5574 set iddrawn($id) 1
5575 incr nrows_drawn
5577 if {$markingmatches} {
5578 markrowmatches $row $id
5582 proc drawcommits {row {endrow {}}} {
5583 global numcommits iddrawn displayorder curview need_redisplay
5584 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5586 if {$row < 0} {
5587 set row 0
5589 if {$endrow eq {}} {
5590 set endrow $row
5592 if {$endrow >= $numcommits} {
5593 set endrow [expr {$numcommits - 1}]
5596 set rl1 [expr {$row - $downarrowlen - 3}]
5597 if {$rl1 < 0} {
5598 set rl1 0
5600 set ro1 [expr {$row - 3}]
5601 if {$ro1 < 0} {
5602 set ro1 0
5604 set r2 [expr {$endrow + $uparrowlen + 3}]
5605 if {$r2 > $numcommits} {
5606 set r2 $numcommits
5608 for {set r $rl1} {$r < $r2} {incr r} {
5609 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5610 if {$rl1 < $r} {
5611 layoutrows $rl1 $r
5613 set rl1 [expr {$r + 1}]
5616 if {$rl1 < $r} {
5617 layoutrows $rl1 $r
5619 optimize_rows $ro1 0 $r2
5620 if {$need_redisplay || $nrows_drawn > 2000} {
5621 clear_display
5622 drawvisible
5625 # make the lines join to already-drawn rows either side
5626 set r [expr {$row - 1}]
5627 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5628 set r $row
5630 set er [expr {$endrow + 1}]
5631 if {$er >= $numcommits ||
5632 ![info exists iddrawn([lindex $displayorder $er])]} {
5633 set er $endrow
5635 for {} {$r <= $er} {incr r} {
5636 set id [lindex $displayorder $r]
5637 set wasdrawn [info exists iddrawn($id)]
5638 drawcmitrow $r
5639 if {$r == $er} break
5640 set nextid [lindex $displayorder [expr {$r + 1}]]
5641 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5642 drawparentlinks $id $r
5644 set rowids [lindex $rowidlist $r]
5645 foreach lid $rowids {
5646 if {$lid eq {}} continue
5647 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5648 if {$lid eq $id} {
5649 # see if this is the first child of any of its parents
5650 foreach p [lindex $parentlist $r] {
5651 if {[lsearch -exact $rowids $p] < 0} {
5652 # make this line extend up to the child
5653 set lineend($p) [drawlineseg $p $r $er 0]
5656 } else {
5657 set lineend($lid) [drawlineseg $lid $r $er 1]
5663 proc undolayout {row} {
5664 global uparrowlen mingaplen downarrowlen
5665 global rowidlist rowisopt rowfinal need_redisplay
5667 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5668 if {$r < 0} {
5669 set r 0
5671 if {[llength $rowidlist] > $r} {
5672 incr r -1
5673 set rowidlist [lrange $rowidlist 0 $r]
5674 set rowfinal [lrange $rowfinal 0 $r]
5675 set rowisopt [lrange $rowisopt 0 $r]
5676 set need_redisplay 1
5677 run drawvisible
5681 proc drawvisible {} {
5682 global canv linespc curview vrowmod selectedline targetrow targetid
5683 global need_redisplay cscroll numcommits
5685 set fs [$canv yview]
5686 set ymax [lindex [$canv cget -scrollregion] 3]
5687 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5688 set f0 [lindex $fs 0]
5689 set f1 [lindex $fs 1]
5690 set y0 [expr {int($f0 * $ymax)}]
5691 set y1 [expr {int($f1 * $ymax)}]
5693 if {[info exists targetid]} {
5694 if {[commitinview $targetid $curview]} {
5695 set r [rowofcommit $targetid]
5696 if {$r != $targetrow} {
5697 # Fix up the scrollregion and change the scrolling position
5698 # now that our target row has moved.
5699 set diff [expr {($r - $targetrow) * $linespc}]
5700 set targetrow $r
5701 setcanvscroll
5702 set ymax [lindex [$canv cget -scrollregion] 3]
5703 incr y0 $diff
5704 incr y1 $diff
5705 set f0 [expr {$y0 / $ymax}]
5706 set f1 [expr {$y1 / $ymax}]
5707 allcanvs yview moveto $f0
5708 $cscroll set $f0 $f1
5709 set need_redisplay 1
5711 } else {
5712 unset targetid
5716 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5717 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5718 if {$endrow >= $vrowmod($curview)} {
5719 update_arcrows $curview
5721 if {$selectedline ne {} &&
5722 $row <= $selectedline && $selectedline <= $endrow} {
5723 set targetrow $selectedline
5724 } elseif {[info exists targetid]} {
5725 set targetrow [expr {int(($row + $endrow) / 2)}]
5727 if {[info exists targetrow]} {
5728 if {$targetrow >= $numcommits} {
5729 set targetrow [expr {$numcommits - 1}]
5731 set targetid [commitonrow $targetrow]
5733 drawcommits $row $endrow
5736 proc clear_display {} {
5737 global iddrawn linesegs need_redisplay nrows_drawn
5738 global vhighlights fhighlights nhighlights rhighlights
5739 global linehtag linentag linedtag boldrows boldnamerows
5741 allcanvs delete all
5742 catch {unset iddrawn}
5743 catch {unset linesegs}
5744 catch {unset linehtag}
5745 catch {unset linentag}
5746 catch {unset linedtag}
5747 set boldrows {}
5748 set boldnamerows {}
5749 catch {unset vhighlights}
5750 catch {unset fhighlights}
5751 catch {unset nhighlights}
5752 catch {unset rhighlights}
5753 set need_redisplay 0
5754 set nrows_drawn 0
5757 proc findcrossings {id} {
5758 global rowidlist parentlist numcommits displayorder
5760 set cross {}
5761 set ccross {}
5762 foreach {s e} [rowranges $id] {
5763 if {$e >= $numcommits} {
5764 set e [expr {$numcommits - 1}]
5766 if {$e <= $s} continue
5767 for {set row $e} {[incr row -1] >= $s} {} {
5768 set x [lsearch -exact [lindex $rowidlist $row] $id]
5769 if {$x < 0} break
5770 set olds [lindex $parentlist $row]
5771 set kid [lindex $displayorder $row]
5772 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5773 if {$kidx < 0} continue
5774 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5775 foreach p $olds {
5776 set px [lsearch -exact $nextrow $p]
5777 if {$px < 0} continue
5778 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5779 if {[lsearch -exact $ccross $p] >= 0} continue
5780 if {$x == $px + ($kidx < $px? -1: 1)} {
5781 lappend ccross $p
5782 } elseif {[lsearch -exact $cross $p] < 0} {
5783 lappend cross $p
5789 return [concat $ccross {{}} $cross]
5792 proc assigncolor {id} {
5793 global colormap colors nextcolor
5794 global parents children children curview
5796 if {[info exists colormap($id)]} return
5797 set ncolors [llength $colors]
5798 if {[info exists children($curview,$id)]} {
5799 set kids $children($curview,$id)
5800 } else {
5801 set kids {}
5803 if {[llength $kids] == 1} {
5804 set child [lindex $kids 0]
5805 if {[info exists colormap($child)]
5806 && [llength $parents($curview,$child)] == 1} {
5807 set colormap($id) $colormap($child)
5808 return
5811 set badcolors {}
5812 set origbad {}
5813 foreach x [findcrossings $id] {
5814 if {$x eq {}} {
5815 # delimiter between corner crossings and other crossings
5816 if {[llength $badcolors] >= $ncolors - 1} break
5817 set origbad $badcolors
5819 if {[info exists colormap($x)]
5820 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5821 lappend badcolors $colormap($x)
5824 if {[llength $badcolors] >= $ncolors} {
5825 set badcolors $origbad
5827 set origbad $badcolors
5828 if {[llength $badcolors] < $ncolors - 1} {
5829 foreach child $kids {
5830 if {[info exists colormap($child)]
5831 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5832 lappend badcolors $colormap($child)
5834 foreach p $parents($curview,$child) {
5835 if {[info exists colormap($p)]
5836 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5837 lappend badcolors $colormap($p)
5841 if {[llength $badcolors] >= $ncolors} {
5842 set badcolors $origbad
5845 for {set i 0} {$i <= $ncolors} {incr i} {
5846 set c [lindex $colors $nextcolor]
5847 if {[incr nextcolor] >= $ncolors} {
5848 set nextcolor 0
5850 if {[lsearch -exact $badcolors $c]} break
5852 set colormap($id) $c
5855 proc bindline {t id} {
5856 global canv
5858 $canv bind $t <Enter> "lineenter %x %y $id"
5859 $canv bind $t <Motion> "linemotion %x %y $id"
5860 $canv bind $t <Leave> "lineleave $id"
5861 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5864 proc drawtags {id x xt y1} {
5865 global idtags idheads idotherrefs mainhead
5866 global linespc lthickness
5867 global canv rowtextx curview fgcolor bgcolor ctxbut
5869 set marks {}
5870 set ntags 0
5871 set nheads 0
5872 if {[info exists idtags($id)]} {
5873 set marks $idtags($id)
5874 set ntags [llength $marks]
5876 if {[info exists idheads($id)]} {
5877 set marks [concat $marks $idheads($id)]
5878 set nheads [llength $idheads($id)]
5880 if {[info exists idotherrefs($id)]} {
5881 set marks [concat $marks $idotherrefs($id)]
5883 if {$marks eq {}} {
5884 return $xt
5887 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5888 set yt [expr {$y1 - 0.5 * $linespc}]
5889 set yb [expr {$yt + $linespc - 1}]
5890 set xvals {}
5891 set wvals {}
5892 set i -1
5893 foreach tag $marks {
5894 incr i
5895 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5896 set wid [font measure mainfontbold $tag]
5897 } else {
5898 set wid [font measure mainfont $tag]
5900 lappend xvals $xt
5901 lappend wvals $wid
5902 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5904 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5905 -width $lthickness -fill black -tags tag.$id]
5906 $canv lower $t
5907 foreach tag $marks x $xvals wid $wvals {
5908 set xl [expr {$x + $delta}]
5909 set xr [expr {$x + $delta + $wid + $lthickness}]
5910 set font mainfont
5911 if {[incr ntags -1] >= 0} {
5912 # draw a tag
5913 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5914 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5915 -width 1 -outline black -fill yellow -tags tag.$id]
5916 $canv bind $t <1> [list showtag $tag 1]
5917 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5918 } else {
5919 # draw a head or other ref
5920 if {[incr nheads -1] >= 0} {
5921 set col green
5922 if {$tag eq $mainhead} {
5923 set font mainfontbold
5925 } else {
5926 set col "#ddddff"
5928 set xl [expr {$xl - $delta/2}]
5929 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5930 -width 1 -outline black -fill $col -tags tag.$id
5931 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5932 set rwid [font measure mainfont $remoteprefix]
5933 set xi [expr {$x + 1}]
5934 set yti [expr {$yt + 1}]
5935 set xri [expr {$x + $rwid}]
5936 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5937 -width 0 -fill "#ffddaa" -tags tag.$id
5940 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5941 -font $font -tags [list tag.$id text]]
5942 if {$ntags >= 0} {
5943 $canv bind $t <1> [list showtag $tag 1]
5944 } elseif {$nheads >= 0} {
5945 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
5948 return $xt
5951 proc xcoord {i level ln} {
5952 global canvx0 xspc1 xspc2
5954 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5955 if {$i > 0 && $i == $level} {
5956 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5957 } elseif {$i > $level} {
5958 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5960 return $x
5963 proc show_status {msg} {
5964 global canv fgcolor
5966 clear_display
5967 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5968 -tags text -fill $fgcolor
5971 # Don't change the text pane cursor if it is currently the hand cursor,
5972 # showing that we are over a sha1 ID link.
5973 proc settextcursor {c} {
5974 global ctext curtextcursor
5976 if {[$ctext cget -cursor] == $curtextcursor} {
5977 $ctext config -cursor $c
5979 set curtextcursor $c
5982 proc nowbusy {what {name {}}} {
5983 global isbusy busyname statusw
5985 if {[array names isbusy] eq {}} {
5986 . config -cursor watch
5987 settextcursor watch
5989 set isbusy($what) 1
5990 set busyname($what) $name
5991 if {$name ne {}} {
5992 $statusw conf -text $name
5996 proc notbusy {what} {
5997 global isbusy maincursor textcursor busyname statusw
5999 catch {
6000 unset isbusy($what)
6001 if {$busyname($what) ne {} &&
6002 [$statusw cget -text] eq $busyname($what)} {
6003 $statusw conf -text {}
6006 if {[array names isbusy] eq {}} {
6007 . config -cursor $maincursor
6008 settextcursor $textcursor
6012 proc findmatches {f} {
6013 global findtype findstring
6014 if {$findtype == [mc "Regexp"]} {
6015 set matches [regexp -indices -all -inline $findstring $f]
6016 } else {
6017 set fs $findstring
6018 if {$findtype == [mc "IgnCase"]} {
6019 set f [string tolower $f]
6020 set fs [string tolower $fs]
6022 set matches {}
6023 set i 0
6024 set l [string length $fs]
6025 while {[set j [string first $fs $f $i]] >= 0} {
6026 lappend matches [list $j [expr {$j+$l-1}]]
6027 set i [expr {$j + $l}]
6030 return $matches
6033 proc dofind {{dirn 1} {wrap 1}} {
6034 global findstring findstartline findcurline selectedline numcommits
6035 global gdttype filehighlight fh_serial find_dirn findallowwrap
6037 if {[info exists find_dirn]} {
6038 if {$find_dirn == $dirn} return
6039 stopfinding
6041 focus .
6042 if {$findstring eq {} || $numcommits == 0} return
6043 if {$selectedline eq {}} {
6044 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
6045 } else {
6046 set findstartline $selectedline
6048 set findcurline $findstartline
6049 nowbusy finding [mc "Searching"]
6050 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
6051 after cancel do_file_hl $fh_serial
6052 do_file_hl $fh_serial
6054 set find_dirn $dirn
6055 set findallowwrap $wrap
6056 run findmore
6059 proc stopfinding {} {
6060 global find_dirn findcurline fprogcoord
6062 if {[info exists find_dirn]} {
6063 unset find_dirn
6064 unset findcurline
6065 notbusy finding
6066 set fprogcoord 0
6067 adjustprogress
6069 stopblaming
6072 proc findmore {} {
6073 global commitdata commitinfo numcommits findpattern findloc
6074 global findstartline findcurline findallowwrap
6075 global find_dirn gdttype fhighlights fprogcoord
6076 global curview varcorder vrownum varccommits vrowmod
6078 if {![info exists find_dirn]} {
6079 return 0
6081 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
6082 set l $findcurline
6083 set moretodo 0
6084 if {$find_dirn > 0} {
6085 incr l
6086 if {$l >= $numcommits} {
6087 set l 0
6089 if {$l <= $findstartline} {
6090 set lim [expr {$findstartline + 1}]
6091 } else {
6092 set lim $numcommits
6093 set moretodo $findallowwrap
6095 } else {
6096 if {$l == 0} {
6097 set l $numcommits
6099 incr l -1
6100 if {$l >= $findstartline} {
6101 set lim [expr {$findstartline - 1}]
6102 } else {
6103 set lim -1
6104 set moretodo $findallowwrap
6107 set n [expr {($lim - $l) * $find_dirn}]
6108 if {$n > 500} {
6109 set n 500
6110 set moretodo 1
6112 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6113 update_arcrows $curview
6115 set found 0
6116 set domore 1
6117 set ai [bsearch $vrownum($curview) $l]
6118 set a [lindex $varcorder($curview) $ai]
6119 set arow [lindex $vrownum($curview) $ai]
6120 set ids [lindex $varccommits($curview,$a)]
6121 set arowend [expr {$arow + [llength $ids]}]
6122 if {$gdttype eq [mc "containing:"]} {
6123 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6124 if {$l < $arow || $l >= $arowend} {
6125 incr ai $find_dirn
6126 set a [lindex $varcorder($curview) $ai]
6127 set arow [lindex $vrownum($curview) $ai]
6128 set ids [lindex $varccommits($curview,$a)]
6129 set arowend [expr {$arow + [llength $ids]}]
6131 set id [lindex $ids [expr {$l - $arow}]]
6132 # shouldn't happen unless git log doesn't give all the commits...
6133 if {![info exists commitdata($id)] ||
6134 ![doesmatch $commitdata($id)]} {
6135 continue
6137 if {![info exists commitinfo($id)]} {
6138 getcommit $id
6140 set info $commitinfo($id)
6141 foreach f $info ty $fldtypes {
6142 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6143 [doesmatch $f]} {
6144 set found 1
6145 break
6148 if {$found} break
6150 } else {
6151 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6152 if {$l < $arow || $l >= $arowend} {
6153 incr ai $find_dirn
6154 set a [lindex $varcorder($curview) $ai]
6155 set arow [lindex $vrownum($curview) $ai]
6156 set ids [lindex $varccommits($curview,$a)]
6157 set arowend [expr {$arow + [llength $ids]}]
6159 set id [lindex $ids [expr {$l - $arow}]]
6160 if {![info exists fhighlights($id)]} {
6161 # this sets fhighlights($id) to -1
6162 askfilehighlight $l $id
6164 if {$fhighlights($id) > 0} {
6165 set found $domore
6166 break
6168 if {$fhighlights($id) < 0} {
6169 if {$domore} {
6170 set domore 0
6171 set findcurline [expr {$l - $find_dirn}]
6176 if {$found || ($domore && !$moretodo)} {
6177 unset findcurline
6178 unset find_dirn
6179 notbusy finding
6180 set fprogcoord 0
6181 adjustprogress
6182 if {$found} {
6183 findselectline $l
6184 } else {
6185 bell
6187 return 0
6189 if {!$domore} {
6190 flushhighlights
6191 } else {
6192 set findcurline [expr {$l - $find_dirn}]
6194 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6195 if {$n < 0} {
6196 incr n $numcommits
6198 set fprogcoord [expr {$n * 1.0 / $numcommits}]
6199 adjustprogress
6200 return $domore
6203 proc findselectline {l} {
6204 global findloc commentend ctext findcurline markingmatches gdttype
6206 set markingmatches 1
6207 set findcurline $l
6208 selectline $l 1
6209 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
6210 # highlight the matches in the comments
6211 set f [$ctext get 1.0 $commentend]
6212 set matches [findmatches $f]
6213 foreach match $matches {
6214 set start [lindex $match 0]
6215 set end [expr {[lindex $match 1] + 1}]
6216 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6219 drawvisible
6222 # mark the bits of a headline or author that match a find string
6223 proc markmatches {canv l str tag matches font row} {
6224 global selectedline
6226 set bbox [$canv bbox $tag]
6227 set x0 [lindex $bbox 0]
6228 set y0 [lindex $bbox 1]
6229 set y1 [lindex $bbox 3]
6230 foreach match $matches {
6231 set start [lindex $match 0]
6232 set end [lindex $match 1]
6233 if {$start > $end} continue
6234 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6235 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6236 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6237 [expr {$x0+$xlen+2}] $y1 \
6238 -outline {} -tags [list match$l matches] -fill yellow]
6239 $canv lower $t
6240 if {$row == $selectedline} {
6241 $canv raise $t secsel
6246 proc unmarkmatches {} {
6247 global markingmatches
6249 allcanvs delete matches
6250 set markingmatches 0
6251 stopfinding
6254 proc selcanvline {w x y} {
6255 global canv canvy0 ctext linespc
6256 global rowtextx
6257 set ymax [lindex [$canv cget -scrollregion] 3]
6258 if {$ymax == {}} return
6259 set yfrac [lindex [$canv yview] 0]
6260 set y [expr {$y + $yfrac * $ymax}]
6261 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6262 if {$l < 0} {
6263 set l 0
6265 if {$w eq $canv} {
6266 set xmax [lindex [$canv cget -scrollregion] 2]
6267 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6268 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6270 unmarkmatches
6271 selectline $l 1
6274 proc commit_descriptor {p} {
6275 global commitinfo
6276 if {![info exists commitinfo($p)]} {
6277 getcommit $p
6279 set l "..."
6280 if {[llength $commitinfo($p)] > 1} {
6281 set l [lindex $commitinfo($p) 0]
6283 return "$p ($l)\n"
6286 # append some text to the ctext widget, and make any SHA1 ID
6287 # that we know about be a clickable link.
6288 proc appendwithlinks {text tags} {
6289 global ctext linknum curview
6291 set start [$ctext index "end - 1c"]
6292 $ctext insert end $text $tags
6293 set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6294 foreach l $links {
6295 set s [lindex $l 0]
6296 set e [lindex $l 1]
6297 set linkid [string range $text $s $e]
6298 incr e
6299 $ctext tag delete link$linknum
6300 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6301 setlink $linkid link$linknum
6302 incr linknum
6306 proc setlink {id lk} {
6307 global curview ctext pendinglinks
6309 set known 0
6310 if {[string length $id] < 40} {
6311 set matches [longid $id]
6312 if {[llength $matches] > 0} {
6313 if {[llength $matches] > 1} return
6314 set known 1
6315 set id [lindex $matches 0]
6317 } else {
6318 set known [commitinview $id $curview]
6320 if {$known} {
6321 $ctext tag conf $lk -foreground blue -underline 1
6322 $ctext tag bind $lk <1> [list selbyid $id]
6323 $ctext tag bind $lk <Enter> {linkcursor %W 1}
6324 $ctext tag bind $lk <Leave> {linkcursor %W -1}
6325 } else {
6326 lappend pendinglinks($id) $lk
6327 interestedin $id {makelink %P}
6331 proc makelink {id} {
6332 global pendinglinks
6334 if {![info exists pendinglinks($id)]} return
6335 foreach lk $pendinglinks($id) {
6336 setlink $id $lk
6338 unset pendinglinks($id)
6341 proc linkcursor {w inc} {
6342 global linkentercount curtextcursor
6344 if {[incr linkentercount $inc] > 0} {
6345 $w configure -cursor hand2
6346 } else {
6347 $w configure -cursor $curtextcursor
6348 if {$linkentercount < 0} {
6349 set linkentercount 0
6354 proc viewnextline {dir} {
6355 global canv linespc
6357 $canv delete hover
6358 set ymax [lindex [$canv cget -scrollregion] 3]
6359 set wnow [$canv yview]
6360 set wtop [expr {[lindex $wnow 0] * $ymax}]
6361 set newtop [expr {$wtop + $dir * $linespc}]
6362 if {$newtop < 0} {
6363 set newtop 0
6364 } elseif {$newtop > $ymax} {
6365 set newtop $ymax
6367 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6370 # add a list of tag or branch names at position pos
6371 # returns the number of names inserted
6372 proc appendrefs {pos ids var} {
6373 global ctext linknum curview $var maxrefs
6375 if {[catch {$ctext index $pos}]} {
6376 return 0
6378 $ctext conf -state normal
6379 $ctext delete $pos "$pos lineend"
6380 set tags {}
6381 foreach id $ids {
6382 foreach tag [set $var\($id\)] {
6383 lappend tags [list $tag $id]
6386 if {[llength $tags] > $maxrefs} {
6387 $ctext insert $pos "many ([llength $tags])"
6388 } else {
6389 set tags [lsort -index 0 -decreasing $tags]
6390 set sep {}
6391 foreach ti $tags {
6392 set id [lindex $ti 1]
6393 set lk link$linknum
6394 incr linknum
6395 $ctext tag delete $lk
6396 $ctext insert $pos $sep
6397 $ctext insert $pos [lindex $ti 0] $lk
6398 setlink $id $lk
6399 set sep ", "
6402 $ctext conf -state disabled
6403 return [llength $tags]
6406 # called when we have finished computing the nearby tags
6407 proc dispneartags {delay} {
6408 global selectedline currentid showneartags tagphase
6410 if {$selectedline eq {} || !$showneartags} return
6411 after cancel dispnexttag
6412 if {$delay} {
6413 after 200 dispnexttag
6414 set tagphase -1
6415 } else {
6416 after idle dispnexttag
6417 set tagphase 0
6421 proc dispnexttag {} {
6422 global selectedline currentid showneartags tagphase ctext
6424 if {$selectedline eq {} || !$showneartags} return
6425 switch -- $tagphase {
6427 set dtags [desctags $currentid]
6428 if {$dtags ne {}} {
6429 appendrefs precedes $dtags idtags
6433 set atags [anctags $currentid]
6434 if {$atags ne {}} {
6435 appendrefs follows $atags idtags
6439 set dheads [descheads $currentid]
6440 if {$dheads ne {}} {
6441 if {[appendrefs branch $dheads idheads] > 1
6442 && [$ctext get "branch -3c"] eq "h"} {
6443 # turn "Branch" into "Branches"
6444 $ctext conf -state normal
6445 $ctext insert "branch -2c" "es"
6446 $ctext conf -state disabled
6451 if {[incr tagphase] <= 2} {
6452 after idle dispnexttag
6456 proc make_secsel {l} {
6457 global linehtag linentag linedtag canv canv2 canv3
6459 if {![info exists linehtag($l)]} return
6460 $canv delete secsel
6461 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
6462 -tags secsel -fill [$canv cget -selectbackground]]
6463 $canv lower $t
6464 $canv2 delete secsel
6465 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
6466 -tags secsel -fill [$canv2 cget -selectbackground]]
6467 $canv2 lower $t
6468 $canv3 delete secsel
6469 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
6470 -tags secsel -fill [$canv3 cget -selectbackground]]
6471 $canv3 lower $t
6474 proc selectline {l isnew {desired_loc {}}} {
6475 global canv ctext commitinfo selectedline
6476 global canvy0 linespc parents children curview
6477 global currentid sha1entry
6478 global commentend idtags linknum
6479 global mergemax numcommits pending_select
6480 global cmitmode showneartags allcommits
6481 global targetrow targetid lastscrollrows
6482 global autoselect jump_to_here
6484 catch {unset pending_select}
6485 $canv delete hover
6486 normalline
6487 unsel_reflist
6488 stopfinding
6489 if {$l < 0 || $l >= $numcommits} return
6490 set id [commitonrow $l]
6491 set targetid $id
6492 set targetrow $l
6493 set selectedline $l
6494 set currentid $id
6495 if {$lastscrollrows < $numcommits} {
6496 setcanvscroll
6499 set y [expr {$canvy0 + $l * $linespc}]
6500 set ymax [lindex [$canv cget -scrollregion] 3]
6501 set ytop [expr {$y - $linespc - 1}]
6502 set ybot [expr {$y + $linespc + 1}]
6503 set wnow [$canv yview]
6504 set wtop [expr {[lindex $wnow 0] * $ymax}]
6505 set wbot [expr {[lindex $wnow 1] * $ymax}]
6506 set wh [expr {$wbot - $wtop}]
6507 set newtop $wtop
6508 if {$ytop < $wtop} {
6509 if {$ybot < $wtop} {
6510 set newtop [expr {$y - $wh / 2.0}]
6511 } else {
6512 set newtop $ytop
6513 if {$newtop > $wtop - $linespc} {
6514 set newtop [expr {$wtop - $linespc}]
6517 } elseif {$ybot > $wbot} {
6518 if {$ytop > $wbot} {
6519 set newtop [expr {$y - $wh / 2.0}]
6520 } else {
6521 set newtop [expr {$ybot - $wh}]
6522 if {$newtop < $wtop + $linespc} {
6523 set newtop [expr {$wtop + $linespc}]
6527 if {$newtop != $wtop} {
6528 if {$newtop < 0} {
6529 set newtop 0
6531 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6532 drawvisible
6535 make_secsel $l
6537 if {$isnew} {
6538 addtohistory [list selbyid $id]
6541 $sha1entry delete 0 end
6542 $sha1entry insert 0 $id
6543 if {$autoselect} {
6544 $sha1entry selection from 0
6545 $sha1entry selection to end
6547 rhighlight_sel $id
6549 $ctext conf -state normal
6550 clear_ctext
6551 set linknum 0
6552 if {![info exists commitinfo($id)]} {
6553 getcommit $id
6555 set info $commitinfo($id)
6556 set date [formatdate [lindex $info 2]]
6557 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
6558 set date [formatdate [lindex $info 4]]
6559 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
6560 if {[info exists idtags($id)]} {
6561 $ctext insert end [mc "Tags:"]
6562 foreach tag $idtags($id) {
6563 $ctext insert end " $tag"
6565 $ctext insert end "\n"
6568 set headers {}
6569 set olds $parents($curview,$id)
6570 if {[llength $olds] > 1} {
6571 set np 0
6572 foreach p $olds {
6573 if {$np >= $mergemax} {
6574 set tag mmax
6575 } else {
6576 set tag m$np
6578 $ctext insert end "[mc "Parent"]: " $tag
6579 appendwithlinks [commit_descriptor $p] {}
6580 incr np
6582 } else {
6583 foreach p $olds {
6584 append headers "[mc "Parent"]: [commit_descriptor $p]"
6588 foreach c $children($curview,$id) {
6589 append headers "[mc "Child"]: [commit_descriptor $c]"
6592 # make anything that looks like a SHA1 ID be a clickable link
6593 appendwithlinks $headers {}
6594 if {$showneartags} {
6595 if {![info exists allcommits]} {
6596 getallcommits
6598 $ctext insert end "[mc "Branch"]: "
6599 $ctext mark set branch "end -1c"
6600 $ctext mark gravity branch left
6601 $ctext insert end "\n[mc "Follows"]: "
6602 $ctext mark set follows "end -1c"
6603 $ctext mark gravity follows left
6604 $ctext insert end "\n[mc "Precedes"]: "
6605 $ctext mark set precedes "end -1c"
6606 $ctext mark gravity precedes left
6607 $ctext insert end "\n"
6608 dispneartags 1
6610 $ctext insert end "\n"
6611 set comment [lindex $info 5]
6612 if {[string first "\r" $comment] >= 0} {
6613 set comment [string map {"\r" "\n "} $comment]
6615 appendwithlinks $comment {comment}
6617 $ctext tag remove found 1.0 end
6618 $ctext conf -state disabled
6619 set commentend [$ctext index "end - 1c"]
6621 set jump_to_here $desired_loc
6622 init_flist [mc "Comments"]
6623 if {$cmitmode eq "tree"} {
6624 gettree $id
6625 } elseif {[llength $olds] <= 1} {
6626 startdiff $id
6627 } else {
6628 mergediff $id
6632 proc selfirstline {} {
6633 unmarkmatches
6634 selectline 0 1
6637 proc sellastline {} {
6638 global numcommits
6639 unmarkmatches
6640 set l [expr {$numcommits - 1}]
6641 selectline $l 1
6644 proc selnextline {dir} {
6645 global selectedline
6646 focus .
6647 if {$selectedline eq {}} return
6648 set l [expr {$selectedline + $dir}]
6649 unmarkmatches
6650 selectline $l 1
6653 proc selnextpage {dir} {
6654 global canv linespc selectedline numcommits
6656 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6657 if {$lpp < 1} {
6658 set lpp 1
6660 allcanvs yview scroll [expr {$dir * $lpp}] units
6661 drawvisible
6662 if {$selectedline eq {}} return
6663 set l [expr {$selectedline + $dir * $lpp}]
6664 if {$l < 0} {
6665 set l 0
6666 } elseif {$l >= $numcommits} {
6667 set l [expr $numcommits - 1]
6669 unmarkmatches
6670 selectline $l 1
6673 proc unselectline {} {
6674 global selectedline currentid
6676 set selectedline {}
6677 catch {unset currentid}
6678 allcanvs delete secsel
6679 rhighlight_none
6682 proc reselectline {} {
6683 global selectedline
6685 if {$selectedline ne {}} {
6686 selectline $selectedline 0
6690 proc addtohistory {cmd} {
6691 global history historyindex curview
6693 set elt [list $curview $cmd]
6694 if {$historyindex > 0
6695 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6696 return
6699 if {$historyindex < [llength $history]} {
6700 set history [lreplace $history $historyindex end $elt]
6701 } else {
6702 lappend history $elt
6704 incr historyindex
6705 if {$historyindex > 1} {
6706 .tf.bar.leftbut conf -state normal
6707 } else {
6708 .tf.bar.leftbut conf -state disabled
6710 .tf.bar.rightbut conf -state disabled
6713 proc godo {elt} {
6714 global curview
6716 set view [lindex $elt 0]
6717 set cmd [lindex $elt 1]
6718 if {$curview != $view} {
6719 showview $view
6721 eval $cmd
6724 proc goback {} {
6725 global history historyindex
6726 focus .
6728 if {$historyindex > 1} {
6729 incr historyindex -1
6730 godo [lindex $history [expr {$historyindex - 1}]]
6731 .tf.bar.rightbut conf -state normal
6733 if {$historyindex <= 1} {
6734 .tf.bar.leftbut conf -state disabled
6738 proc goforw {} {
6739 global history historyindex
6740 focus .
6742 if {$historyindex < [llength $history]} {
6743 set cmd [lindex $history $historyindex]
6744 incr historyindex
6745 godo $cmd
6746 .tf.bar.leftbut conf -state normal
6748 if {$historyindex >= [llength $history]} {
6749 .tf.bar.rightbut conf -state disabled
6753 proc gettree {id} {
6754 global treefilelist treeidlist diffids diffmergeid treepending
6755 global nullid nullid2
6757 set diffids $id
6758 catch {unset diffmergeid}
6759 if {![info exists treefilelist($id)]} {
6760 if {![info exists treepending]} {
6761 if {$id eq $nullid} {
6762 set cmd [list | git ls-files]
6763 } elseif {$id eq $nullid2} {
6764 set cmd [list | git ls-files --stage -t]
6765 } else {
6766 set cmd [list | git ls-tree -r $id]
6768 if {[catch {set gtf [open $cmd r]}]} {
6769 return
6771 set treepending $id
6772 set treefilelist($id) {}
6773 set treeidlist($id) {}
6774 fconfigure $gtf -blocking 0 -encoding binary
6775 filerun $gtf [list gettreeline $gtf $id]
6777 } else {
6778 setfilelist $id
6782 proc gettreeline {gtf id} {
6783 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6785 set nl 0
6786 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6787 if {$diffids eq $nullid} {
6788 set fname $line
6789 } else {
6790 set i [string first "\t" $line]
6791 if {$i < 0} continue
6792 set fname [string range $line [expr {$i+1}] end]
6793 set line [string range $line 0 [expr {$i-1}]]
6794 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6795 set sha1 [lindex $line 2]
6796 lappend treeidlist($id) $sha1
6798 if {[string index $fname 0] eq "\""} {
6799 set fname [lindex $fname 0]
6801 set fname [encoding convertfrom $fname]
6802 lappend treefilelist($id) $fname
6804 if {![eof $gtf]} {
6805 return [expr {$nl >= 1000? 2: 1}]
6807 close $gtf
6808 unset treepending
6809 if {$cmitmode ne "tree"} {
6810 if {![info exists diffmergeid]} {
6811 gettreediffs $diffids
6813 } elseif {$id ne $diffids} {
6814 gettree $diffids
6815 } else {
6816 setfilelist $id
6818 return 0
6821 proc showfile {f} {
6822 global treefilelist treeidlist diffids nullid nullid2
6823 global ctext_file_names ctext_file_lines
6824 global ctext commentend
6826 set i [lsearch -exact $treefilelist($diffids) $f]
6827 if {$i < 0} {
6828 puts "oops, $f not in list for id $diffids"
6829 return
6831 if {$diffids eq $nullid} {
6832 if {[catch {set bf [open $f r]} err]} {
6833 puts "oops, can't read $f: $err"
6834 return
6836 } else {
6837 set blob [lindex $treeidlist($diffids) $i]
6838 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6839 puts "oops, error reading blob $blob: $err"
6840 return
6843 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
6844 filerun $bf [list getblobline $bf $diffids]
6845 $ctext config -state normal
6846 clear_ctext $commentend
6847 lappend ctext_file_names $f
6848 lappend ctext_file_lines [lindex [split $commentend "."] 0]
6849 $ctext insert end "\n"
6850 $ctext insert end "$f\n" filesep
6851 $ctext config -state disabled
6852 $ctext yview $commentend
6853 settabs 0
6856 proc getblobline {bf id} {
6857 global diffids cmitmode ctext
6859 if {$id ne $diffids || $cmitmode ne "tree"} {
6860 catch {close $bf}
6861 return 0
6863 $ctext config -state normal
6864 set nl 0
6865 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6866 $ctext insert end "$line\n"
6868 if {[eof $bf]} {
6869 global jump_to_here ctext_file_names commentend
6871 # delete last newline
6872 $ctext delete "end - 2c" "end - 1c"
6873 close $bf
6874 if {$jump_to_here ne {} &&
6875 [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
6876 set lnum [expr {[lindex $jump_to_here 1] +
6877 [lindex [split $commentend .] 0]}]
6878 mark_ctext_line $lnum
6880 return 0
6882 $ctext config -state disabled
6883 return [expr {$nl >= 1000? 2: 1}]
6886 proc mark_ctext_line {lnum} {
6887 global ctext markbgcolor
6889 $ctext tag delete omark
6890 $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
6891 $ctext tag conf omark -background $markbgcolor
6892 $ctext see $lnum.0
6895 proc mergediff {id} {
6896 global diffmergeid
6897 global diffids treediffs
6898 global parents curview
6900 set diffmergeid $id
6901 set diffids $id
6902 set treediffs($id) {}
6903 set np [llength $parents($curview,$id)]
6904 settabs $np
6905 getblobdiffs $id
6908 proc startdiff {ids} {
6909 global treediffs diffids treepending diffmergeid nullid nullid2
6911 settabs 1
6912 set diffids $ids
6913 catch {unset diffmergeid}
6914 if {![info exists treediffs($ids)] ||
6915 [lsearch -exact $ids $nullid] >= 0 ||
6916 [lsearch -exact $ids $nullid2] >= 0} {
6917 if {![info exists treepending]} {
6918 gettreediffs $ids
6920 } else {
6921 addtocflist $ids
6925 proc path_filter {filter name} {
6926 foreach p $filter {
6927 set l [string length $p]
6928 if {[string index $p end] eq "/"} {
6929 if {[string compare -length $l $p $name] == 0} {
6930 return 1
6932 } else {
6933 if {[string compare -length $l $p $name] == 0 &&
6934 ([string length $name] == $l ||
6935 [string index $name $l] eq "/")} {
6936 return 1
6940 return 0
6943 proc addtocflist {ids} {
6944 global treediffs
6946 add_flist $treediffs($ids)
6947 getblobdiffs $ids
6950 proc diffcmd {ids flags} {
6951 global nullid nullid2
6953 set i [lsearch -exact $ids $nullid]
6954 set j [lsearch -exact $ids $nullid2]
6955 if {$i >= 0} {
6956 if {[llength $ids] > 1 && $j < 0} {
6957 # comparing working directory with some specific revision
6958 set cmd [concat | git diff-index $flags]
6959 if {$i == 0} {
6960 lappend cmd -R [lindex $ids 1]
6961 } else {
6962 lappend cmd [lindex $ids 0]
6964 } else {
6965 # comparing working directory with index
6966 set cmd [concat | git diff-files $flags]
6967 if {$j == 1} {
6968 lappend cmd -R
6971 } elseif {$j >= 0} {
6972 set cmd [concat | git diff-index --cached $flags]
6973 if {[llength $ids] > 1} {
6974 # comparing index with specific revision
6975 if {$i == 0} {
6976 lappend cmd -R [lindex $ids 1]
6977 } else {
6978 lappend cmd [lindex $ids 0]
6980 } else {
6981 # comparing index with HEAD
6982 lappend cmd HEAD
6984 } else {
6985 set cmd [concat | git diff-tree -r $flags $ids]
6987 return $cmd
6990 proc gettreediffs {ids} {
6991 global treediff treepending
6993 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6995 set treepending $ids
6996 set treediff {}
6997 fconfigure $gdtf -blocking 0 -encoding binary
6998 filerun $gdtf [list gettreediffline $gdtf $ids]
7001 proc gettreediffline {gdtf ids} {
7002 global treediff treediffs treepending diffids diffmergeid
7003 global cmitmode vfilelimit curview limitdiffs perfile_attrs
7005 set nr 0
7006 set sublist {}
7007 set max 1000
7008 if {$perfile_attrs} {
7009 # cache_gitattr is slow, and even slower on win32 where we
7010 # have to invoke it for only about 30 paths at a time
7011 set max 500
7012 if {[tk windowingsystem] == "win32"} {
7013 set max 120
7016 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
7017 set i [string first "\t" $line]
7018 if {$i >= 0} {
7019 set file [string range $line [expr {$i+1}] end]
7020 if {[string index $file 0] eq "\""} {
7021 set file [lindex $file 0]
7023 set file [encoding convertfrom $file]
7024 if {$file ne [lindex $treediff end]} {
7025 lappend treediff $file
7026 lappend sublist $file
7030 if {$perfile_attrs} {
7031 cache_gitattr encoding $sublist
7033 if {![eof $gdtf]} {
7034 return [expr {$nr >= $max? 2: 1}]
7036 close $gdtf
7037 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7038 set flist {}
7039 foreach f $treediff {
7040 if {[path_filter $vfilelimit($curview) $f]} {
7041 lappend flist $f
7044 set treediffs($ids) $flist
7045 } else {
7046 set treediffs($ids) $treediff
7048 unset treepending
7049 if {$cmitmode eq "tree"} {
7050 gettree $diffids
7051 } elseif {$ids != $diffids} {
7052 if {![info exists diffmergeid]} {
7053 gettreediffs $diffids
7055 } else {
7056 addtocflist $ids
7058 return 0
7061 # empty string or positive integer
7062 proc diffcontextvalidate {v} {
7063 return [regexp {^(|[1-9][0-9]*)$} $v]
7066 proc diffcontextchange {n1 n2 op} {
7067 global diffcontextstring diffcontext
7069 if {[string is integer -strict $diffcontextstring]} {
7070 if {$diffcontextstring > 0} {
7071 set diffcontext $diffcontextstring
7072 reselectline
7077 proc changeignorespace {} {
7078 reselectline
7081 proc getblobdiffs {ids} {
7082 global blobdifffd diffids env
7083 global diffinhdr treediffs
7084 global diffcontext
7085 global ignorespace
7086 global limitdiffs vfilelimit curview
7087 global diffencoding targetline diffnparents
7089 set cmd [diffcmd $ids "-p -C --cc --no-commit-id -U$diffcontext"]
7090 if {$ignorespace} {
7091 append cmd " -w"
7093 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7094 set cmd [concat $cmd -- $vfilelimit($curview)]
7096 if {[catch {set bdf [open $cmd r]} err]} {
7097 error_popup [mc "Error getting diffs: %s" $err]
7098 return
7100 set targetline {}
7101 set diffnparents 0
7102 set diffinhdr 0
7103 set diffencoding [get_path_encoding {}]
7104 fconfigure $bdf -blocking 0 -encoding binary
7105 set blobdifffd($ids) $bdf
7106 filerun $bdf [list getblobdiffline $bdf $diffids]
7109 proc setinlist {var i val} {
7110 global $var
7112 while {[llength [set $var]] < $i} {
7113 lappend $var {}
7115 if {[llength [set $var]] == $i} {
7116 lappend $var $val
7117 } else {
7118 lset $var $i $val
7122 proc makediffhdr {fname ids} {
7123 global ctext curdiffstart treediffs diffencoding
7124 global ctext_file_names jump_to_here targetline diffline
7126 set fname [encoding convertfrom $fname]
7127 set diffencoding [get_path_encoding $fname]
7128 set i [lsearch -exact $treediffs($ids) $fname]
7129 if {$i >= 0} {
7130 setinlist difffilestart $i $curdiffstart
7132 lset ctext_file_names end $fname
7133 set l [expr {(78 - [string length $fname]) / 2}]
7134 set pad [string range "----------------------------------------" 1 $l]
7135 $ctext insert $curdiffstart "$pad $fname $pad" filesep
7136 set targetline {}
7137 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7138 set targetline [lindex $jump_to_here 1]
7140 set diffline 0
7143 proc getblobdiffline {bdf ids} {
7144 global diffids blobdifffd ctext curdiffstart
7145 global diffnexthead diffnextnote difffilestart
7146 global ctext_file_names ctext_file_lines
7147 global diffinhdr treediffs mergemax diffnparents
7148 global diffencoding jump_to_here targetline diffline
7150 set nr 0
7151 $ctext conf -state normal
7152 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7153 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7154 close $bdf
7155 return 0
7157 if {![string compare -length 5 "diff " $line]} {
7158 if {![regexp {^diff (--cc|--git) } $line m type]} {
7159 set line [encoding convertfrom $line]
7160 $ctext insert end "$line\n" hunksep
7161 continue
7163 # start of a new file
7164 set diffinhdr 1
7165 $ctext insert end "\n"
7166 set curdiffstart [$ctext index "end - 1c"]
7167 lappend ctext_file_names ""
7168 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7169 $ctext insert end "\n" filesep
7171 if {$type eq "--cc"} {
7172 # start of a new file in a merge diff
7173 set fname [string range $line 10 end]
7174 if {[lsearch -exact $treediffs($ids) $fname] < 0} {
7175 lappend treediffs($ids) $fname
7176 add_flist [list $fname]
7179 } else {
7180 set line [string range $line 11 end]
7181 # If the name hasn't changed the length will be odd,
7182 # the middle char will be a space, and the two bits either
7183 # side will be a/name and b/name, or "a/name" and "b/name".
7184 # If the name has changed we'll get "rename from" and
7185 # "rename to" or "copy from" and "copy to" lines following
7186 # this, and we'll use them to get the filenames.
7187 # This complexity is necessary because spaces in the
7188 # filename(s) don't get escaped.
7189 set l [string length $line]
7190 set i [expr {$l / 2}]
7191 if {!(($l & 1) && [string index $line $i] eq " " &&
7192 [string range $line 2 [expr {$i - 1}]] eq \
7193 [string range $line [expr {$i + 3}] end])} {
7194 continue
7196 # unescape if quoted and chop off the a/ from the front
7197 if {[string index $line 0] eq "\""} {
7198 set fname [string range [lindex $line 0] 2 end]
7199 } else {
7200 set fname [string range $line 2 [expr {$i - 1}]]
7203 makediffhdr $fname $ids
7205 } elseif {![string compare -length 16 "* Unmerged path " $line]} {
7206 set fname [encoding convertfrom [string range $line 16 end]]
7207 $ctext insert end "\n"
7208 set curdiffstart [$ctext index "end - 1c"]
7209 lappend ctext_file_names $fname
7210 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7211 $ctext insert end "$line\n" filesep
7212 set i [lsearch -exact $treediffs($ids) $fname]
7213 if {$i >= 0} {
7214 setinlist difffilestart $i $curdiffstart
7217 } elseif {![string compare -length 2 "@@" $line]} {
7218 regexp {^@@+} $line ats
7219 set line [encoding convertfrom $diffencoding $line]
7220 $ctext insert end "$line\n" hunksep
7221 if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
7222 set diffline $nl
7224 set diffnparents [expr {[string length $ats] - 1}]
7225 set diffinhdr 0
7227 } elseif {$diffinhdr} {
7228 if {![string compare -length 12 "rename from " $line]} {
7229 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7230 if {[string index $fname 0] eq "\""} {
7231 set fname [lindex $fname 0]
7233 set fname [encoding convertfrom $fname]
7234 set i [lsearch -exact $treediffs($ids) $fname]
7235 if {$i >= 0} {
7236 setinlist difffilestart $i $curdiffstart
7238 } elseif {![string compare -length 10 $line "rename to "] ||
7239 ![string compare -length 8 $line "copy to "]} {
7240 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7241 if {[string index $fname 0] eq "\""} {
7242 set fname [lindex $fname 0]
7244 makediffhdr $fname $ids
7245 } elseif {[string compare -length 3 $line "---"] == 0} {
7246 # do nothing
7247 continue
7248 } elseif {[string compare -length 3 $line "+++"] == 0} {
7249 set diffinhdr 0
7250 continue
7252 $ctext insert end "$line\n" filesep
7254 } else {
7255 set line [encoding convertfrom $diffencoding $line]
7256 # parse the prefix - one ' ', '-' or '+' for each parent
7257 set prefix [string range $line 0 [expr {$diffnparents - 1}]]
7258 set tag [expr {$diffnparents > 1? "m": "d"}]
7259 if {[string trim $prefix " -+"] eq {}} {
7260 # prefix only has " ", "-" and "+" in it: normal diff line
7261 set num [string first "-" $prefix]
7262 if {$num >= 0} {
7263 # removed line, first parent with line is $num
7264 if {$num >= $mergemax} {
7265 set num "max"
7267 $ctext insert end "$line\n" $tag$num
7268 } else {
7269 set tags {}
7270 if {[string first "+" $prefix] >= 0} {
7271 # added line
7272 lappend tags ${tag}result
7273 if {$diffnparents > 1} {
7274 set num [string first " " $prefix]
7275 if {$num >= 0} {
7276 if {$num >= $mergemax} {
7277 set num "max"
7279 lappend tags m$num
7283 if {$targetline ne {}} {
7284 if {$diffline == $targetline} {
7285 set seehere [$ctext index "end - 1 chars"]
7286 set targetline {}
7287 } else {
7288 incr diffline
7291 $ctext insert end "$line\n" $tags
7293 } else {
7294 # "\ No newline at end of file",
7295 # or something else we don't recognize
7296 $ctext insert end "$line\n" hunksep
7300 if {[info exists seehere]} {
7301 mark_ctext_line [lindex [split $seehere .] 0]
7303 $ctext conf -state disabled
7304 if {[eof $bdf]} {
7305 close $bdf
7306 return 0
7308 return [expr {$nr >= 1000? 2: 1}]
7311 proc changediffdisp {} {
7312 global ctext diffelide
7314 $ctext tag conf d0 -elide [lindex $diffelide 0]
7315 $ctext tag conf dresult -elide [lindex $diffelide 1]
7318 proc highlightfile {loc cline} {
7319 global ctext cflist cflist_top
7321 $ctext yview $loc
7322 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7323 $cflist tag add highlight $cline.0 "$cline.0 lineend"
7324 $cflist see $cline.0
7325 set cflist_top $cline
7328 proc prevfile {} {
7329 global difffilestart ctext cmitmode
7331 if {$cmitmode eq "tree"} return
7332 set prev 0.0
7333 set prevline 1
7334 set here [$ctext index @0,0]
7335 foreach loc $difffilestart {
7336 if {[$ctext compare $loc >= $here]} {
7337 highlightfile $prev $prevline
7338 return
7340 set prev $loc
7341 incr prevline
7343 highlightfile $prev $prevline
7346 proc nextfile {} {
7347 global difffilestart ctext cmitmode
7349 if {$cmitmode eq "tree"} return
7350 set here [$ctext index @0,0]
7351 set line 1
7352 foreach loc $difffilestart {
7353 incr line
7354 if {[$ctext compare $loc > $here]} {
7355 highlightfile $loc $line
7356 return
7361 proc clear_ctext {{first 1.0}} {
7362 global ctext smarktop smarkbot
7363 global ctext_file_names ctext_file_lines
7364 global pendinglinks
7366 set l [lindex [split $first .] 0]
7367 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
7368 set smarktop $l
7370 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
7371 set smarkbot $l
7373 $ctext delete $first end
7374 if {$first eq "1.0"} {
7375 catch {unset pendinglinks}
7377 set ctext_file_names {}
7378 set ctext_file_lines {}
7381 proc settabs {{firstab {}}} {
7382 global firsttabstop tabstop ctext have_tk85
7384 if {$firstab ne {} && $have_tk85} {
7385 set firsttabstop $firstab
7387 set w [font measure textfont "0"]
7388 if {$firsttabstop != 0} {
7389 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
7390 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
7391 } elseif {$have_tk85 || $tabstop != 8} {
7392 $ctext conf -tabs [expr {$tabstop * $w}]
7393 } else {
7394 $ctext conf -tabs {}
7398 proc incrsearch {name ix op} {
7399 global ctext searchstring searchdirn
7401 $ctext tag remove found 1.0 end
7402 if {[catch {$ctext index anchor}]} {
7403 # no anchor set, use start of selection, or of visible area
7404 set sel [$ctext tag ranges sel]
7405 if {$sel ne {}} {
7406 $ctext mark set anchor [lindex $sel 0]
7407 } elseif {$searchdirn eq "-forwards"} {
7408 $ctext mark set anchor @0,0
7409 } else {
7410 $ctext mark set anchor @0,[winfo height $ctext]
7413 if {$searchstring ne {}} {
7414 set here [$ctext search $searchdirn -- $searchstring anchor]
7415 if {$here ne {}} {
7416 $ctext see $here
7418 searchmarkvisible 1
7422 proc dosearch {} {
7423 global sstring ctext searchstring searchdirn
7425 focus $sstring
7426 $sstring icursor end
7427 set searchdirn -forwards
7428 if {$searchstring ne {}} {
7429 set sel [$ctext tag ranges sel]
7430 if {$sel ne {}} {
7431 set start "[lindex $sel 0] + 1c"
7432 } elseif {[catch {set start [$ctext index anchor]}]} {
7433 set start "@0,0"
7435 set match [$ctext search -count mlen -- $searchstring $start]
7436 $ctext tag remove sel 1.0 end
7437 if {$match eq {}} {
7438 bell
7439 return
7441 $ctext see $match
7442 set mend "$match + $mlen c"
7443 $ctext tag add sel $match $mend
7444 $ctext mark unset anchor
7448 proc dosearchback {} {
7449 global sstring ctext searchstring searchdirn
7451 focus $sstring
7452 $sstring icursor end
7453 set searchdirn -backwards
7454 if {$searchstring ne {}} {
7455 set sel [$ctext tag ranges sel]
7456 if {$sel ne {}} {
7457 set start [lindex $sel 0]
7458 } elseif {[catch {set start [$ctext index anchor]}]} {
7459 set start @0,[winfo height $ctext]
7461 set match [$ctext search -backwards -count ml -- $searchstring $start]
7462 $ctext tag remove sel 1.0 end
7463 if {$match eq {}} {
7464 bell
7465 return
7467 $ctext see $match
7468 set mend "$match + $ml c"
7469 $ctext tag add sel $match $mend
7470 $ctext mark unset anchor
7474 proc searchmark {first last} {
7475 global ctext searchstring
7477 set mend $first.0
7478 while {1} {
7479 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
7480 if {$match eq {}} break
7481 set mend "$match + $mlen c"
7482 $ctext tag add found $match $mend
7486 proc searchmarkvisible {doall} {
7487 global ctext smarktop smarkbot
7489 set topline [lindex [split [$ctext index @0,0] .] 0]
7490 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
7491 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
7492 # no overlap with previous
7493 searchmark $topline $botline
7494 set smarktop $topline
7495 set smarkbot $botline
7496 } else {
7497 if {$topline < $smarktop} {
7498 searchmark $topline [expr {$smarktop-1}]
7499 set smarktop $topline
7501 if {$botline > $smarkbot} {
7502 searchmark [expr {$smarkbot+1}] $botline
7503 set smarkbot $botline
7508 proc scrolltext {f0 f1} {
7509 global searchstring
7511 .bleft.bottom.sb set $f0 $f1
7512 if {$searchstring ne {}} {
7513 searchmarkvisible 0
7517 proc setcoords {} {
7518 global linespc charspc canvx0 canvy0
7519 global xspc1 xspc2 lthickness
7521 set linespc [font metrics mainfont -linespace]
7522 set charspc [font measure mainfont "m"]
7523 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
7524 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
7525 set lthickness [expr {int($linespc / 9) + 1}]
7526 set xspc1(0) $linespc
7527 set xspc2 $linespc
7530 proc redisplay {} {
7531 global canv
7532 global selectedline
7534 set ymax [lindex [$canv cget -scrollregion] 3]
7535 if {$ymax eq {} || $ymax == 0} return
7536 set span [$canv yview]
7537 clear_display
7538 setcanvscroll
7539 allcanvs yview moveto [lindex $span 0]
7540 drawvisible
7541 if {$selectedline ne {}} {
7542 selectline $selectedline 0
7543 allcanvs yview moveto [lindex $span 0]
7547 proc parsefont {f n} {
7548 global fontattr
7550 set fontattr($f,family) [lindex $n 0]
7551 set s [lindex $n 1]
7552 if {$s eq {} || $s == 0} {
7553 set s 10
7554 } elseif {$s < 0} {
7555 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
7557 set fontattr($f,size) $s
7558 set fontattr($f,weight) normal
7559 set fontattr($f,slant) roman
7560 foreach style [lrange $n 2 end] {
7561 switch -- $style {
7562 "normal" -
7563 "bold" {set fontattr($f,weight) $style}
7564 "roman" -
7565 "italic" {set fontattr($f,slant) $style}
7570 proc fontflags {f {isbold 0}} {
7571 global fontattr
7573 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
7574 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
7575 -slant $fontattr($f,slant)]
7578 proc fontname {f} {
7579 global fontattr
7581 set n [list $fontattr($f,family) $fontattr($f,size)]
7582 if {$fontattr($f,weight) eq "bold"} {
7583 lappend n "bold"
7585 if {$fontattr($f,slant) eq "italic"} {
7586 lappend n "italic"
7588 return $n
7591 proc incrfont {inc} {
7592 global mainfont textfont ctext canv cflist showrefstop
7593 global stopped entries fontattr
7595 unmarkmatches
7596 set s $fontattr(mainfont,size)
7597 incr s $inc
7598 if {$s < 1} {
7599 set s 1
7601 set fontattr(mainfont,size) $s
7602 font config mainfont -size $s
7603 font config mainfontbold -size $s
7604 set mainfont [fontname mainfont]
7605 set s $fontattr(textfont,size)
7606 incr s $inc
7607 if {$s < 1} {
7608 set s 1
7610 set fontattr(textfont,size) $s
7611 font config textfont -size $s
7612 font config textfontbold -size $s
7613 set textfont [fontname textfont]
7614 setcoords
7615 settabs
7616 redisplay
7619 proc clearsha1 {} {
7620 global sha1entry sha1string
7621 if {[string length $sha1string] == 40} {
7622 $sha1entry delete 0 end
7626 proc sha1change {n1 n2 op} {
7627 global sha1string currentid sha1but
7628 if {$sha1string == {}
7629 || ([info exists currentid] && $sha1string == $currentid)} {
7630 set state disabled
7631 } else {
7632 set state normal
7634 if {[$sha1but cget -state] == $state} return
7635 if {$state == "normal"} {
7636 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
7637 } else {
7638 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
7642 proc gotocommit {} {
7643 global sha1string tagids headids curview varcid
7645 if {$sha1string == {}
7646 || ([info exists currentid] && $sha1string == $currentid)} return
7647 if {[info exists tagids($sha1string)]} {
7648 set id $tagids($sha1string)
7649 } elseif {[info exists headids($sha1string)]} {
7650 set id $headids($sha1string)
7651 } else {
7652 set id [string tolower $sha1string]
7653 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7654 set matches [longid $id]
7655 if {$matches ne {}} {
7656 if {[llength $matches] > 1} {
7657 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7658 return
7660 set id [lindex $matches 0]
7664 if {[commitinview $id $curview]} {
7665 selectline [rowofcommit $id] 1
7666 return
7668 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7669 set msg [mc "SHA1 id %s is not known" $sha1string]
7670 } else {
7671 set msg [mc "Tag/Head %s is not known" $sha1string]
7673 error_popup $msg
7676 proc lineenter {x y id} {
7677 global hoverx hovery hoverid hovertimer
7678 global commitinfo canv
7680 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7681 set hoverx $x
7682 set hovery $y
7683 set hoverid $id
7684 if {[info exists hovertimer]} {
7685 after cancel $hovertimer
7687 set hovertimer [after 500 linehover]
7688 $canv delete hover
7691 proc linemotion {x y id} {
7692 global hoverx hovery hoverid hovertimer
7694 if {[info exists hoverid] && $id == $hoverid} {
7695 set hoverx $x
7696 set hovery $y
7697 if {[info exists hovertimer]} {
7698 after cancel $hovertimer
7700 set hovertimer [after 500 linehover]
7704 proc lineleave {id} {
7705 global hoverid hovertimer canv
7707 if {[info exists hoverid] && $id == $hoverid} {
7708 $canv delete hover
7709 if {[info exists hovertimer]} {
7710 after cancel $hovertimer
7711 unset hovertimer
7713 unset hoverid
7717 proc linehover {} {
7718 global hoverx hovery hoverid hovertimer
7719 global canv linespc lthickness
7720 global commitinfo
7722 set text [lindex $commitinfo($hoverid) 0]
7723 set ymax [lindex [$canv cget -scrollregion] 3]
7724 if {$ymax == {}} return
7725 set yfrac [lindex [$canv yview] 0]
7726 set x [expr {$hoverx + 2 * $linespc}]
7727 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7728 set x0 [expr {$x - 2 * $lthickness}]
7729 set y0 [expr {$y - 2 * $lthickness}]
7730 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7731 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7732 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7733 -fill \#ffff80 -outline black -width 1 -tags hover]
7734 $canv raise $t
7735 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7736 -font mainfont]
7737 $canv raise $t
7740 proc clickisonarrow {id y} {
7741 global lthickness
7743 set ranges [rowranges $id]
7744 set thresh [expr {2 * $lthickness + 6}]
7745 set n [expr {[llength $ranges] - 1}]
7746 for {set i 1} {$i < $n} {incr i} {
7747 set row [lindex $ranges $i]
7748 if {abs([yc $row] - $y) < $thresh} {
7749 return $i
7752 return {}
7755 proc arrowjump {id n y} {
7756 global canv
7758 # 1 <-> 2, 3 <-> 4, etc...
7759 set n [expr {(($n - 1) ^ 1) + 1}]
7760 set row [lindex [rowranges $id] $n]
7761 set yt [yc $row]
7762 set ymax [lindex [$canv cget -scrollregion] 3]
7763 if {$ymax eq {} || $ymax <= 0} return
7764 set view [$canv yview]
7765 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7766 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7767 if {$yfrac < 0} {
7768 set yfrac 0
7770 allcanvs yview moveto $yfrac
7773 proc lineclick {x y id isnew} {
7774 global ctext commitinfo children canv thickerline curview
7776 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7777 unmarkmatches
7778 unselectline
7779 normalline
7780 $canv delete hover
7781 # draw this line thicker than normal
7782 set thickerline $id
7783 drawlines $id
7784 if {$isnew} {
7785 set ymax [lindex [$canv cget -scrollregion] 3]
7786 if {$ymax eq {}} return
7787 set yfrac [lindex [$canv yview] 0]
7788 set y [expr {$y + $yfrac * $ymax}]
7790 set dirn [clickisonarrow $id $y]
7791 if {$dirn ne {}} {
7792 arrowjump $id $dirn $y
7793 return
7796 if {$isnew} {
7797 addtohistory [list lineclick $x $y $id 0]
7799 # fill the details pane with info about this line
7800 $ctext conf -state normal
7801 clear_ctext
7802 settabs 0
7803 $ctext insert end "[mc "Parent"]:\t"
7804 $ctext insert end $id link0
7805 setlink $id link0
7806 set info $commitinfo($id)
7807 $ctext insert end "\n\t[lindex $info 0]\n"
7808 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7809 set date [formatdate [lindex $info 2]]
7810 $ctext insert end "\t[mc "Date"]:\t$date\n"
7811 set kids $children($curview,$id)
7812 if {$kids ne {}} {
7813 $ctext insert end "\n[mc "Children"]:"
7814 set i 0
7815 foreach child $kids {
7816 incr i
7817 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7818 set info $commitinfo($child)
7819 $ctext insert end "\n\t"
7820 $ctext insert end $child link$i
7821 setlink $child link$i
7822 $ctext insert end "\n\t[lindex $info 0]"
7823 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7824 set date [formatdate [lindex $info 2]]
7825 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7828 $ctext conf -state disabled
7829 init_flist {}
7832 proc normalline {} {
7833 global thickerline
7834 if {[info exists thickerline]} {
7835 set id $thickerline
7836 unset thickerline
7837 drawlines $id
7841 proc selbyid {id} {
7842 global curview
7843 if {[commitinview $id $curview]} {
7844 selectline [rowofcommit $id] 1
7848 proc mstime {} {
7849 global startmstime
7850 if {![info exists startmstime]} {
7851 set startmstime [clock clicks -milliseconds]
7853 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7856 proc rowmenu {x y id} {
7857 global rowctxmenu selectedline rowmenuid curview
7858 global nullid nullid2 fakerowmenu mainhead
7860 stopfinding
7861 set rowmenuid $id
7862 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7863 set state disabled
7864 } else {
7865 set state normal
7867 if {$id ne $nullid && $id ne $nullid2} {
7868 set menu $rowctxmenu
7869 if {$mainhead ne {}} {
7870 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7871 } else {
7872 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7874 } else {
7875 set menu $fakerowmenu
7877 $menu entryconfigure [mca "Diff this -> selected"] -state $state
7878 $menu entryconfigure [mca "Diff selected -> this"] -state $state
7879 $menu entryconfigure [mca "Make patch"] -state $state
7880 tk_popup $menu $x $y
7883 proc diffvssel {dirn} {
7884 global rowmenuid selectedline
7886 if {$selectedline eq {}} return
7887 if {$dirn} {
7888 set oldid [commitonrow $selectedline]
7889 set newid $rowmenuid
7890 } else {
7891 set oldid $rowmenuid
7892 set newid [commitonrow $selectedline]
7894 addtohistory [list doseldiff $oldid $newid]
7895 doseldiff $oldid $newid
7898 proc doseldiff {oldid newid} {
7899 global ctext
7900 global commitinfo
7902 $ctext conf -state normal
7903 clear_ctext
7904 init_flist [mc "Top"]
7905 $ctext insert end "[mc "From"] "
7906 $ctext insert end $oldid link0
7907 setlink $oldid link0
7908 $ctext insert end "\n "
7909 $ctext insert end [lindex $commitinfo($oldid) 0]
7910 $ctext insert end "\n\n[mc "To"] "
7911 $ctext insert end $newid link1
7912 setlink $newid link1
7913 $ctext insert end "\n "
7914 $ctext insert end [lindex $commitinfo($newid) 0]
7915 $ctext insert end "\n"
7916 $ctext conf -state disabled
7917 $ctext tag remove found 1.0 end
7918 startdiff [list $oldid $newid]
7921 proc mkpatch {} {
7922 global rowmenuid currentid commitinfo patchtop patchnum
7924 if {![info exists currentid]} return
7925 set oldid $currentid
7926 set oldhead [lindex $commitinfo($oldid) 0]
7927 set newid $rowmenuid
7928 set newhead [lindex $commitinfo($newid) 0]
7929 set top .patch
7930 set patchtop $top
7931 catch {destroy $top}
7932 toplevel $top
7933 make_transient $top .
7934 label $top.title -text [mc "Generate patch"]
7935 grid $top.title - -pady 10
7936 label $top.from -text [mc "From:"]
7937 entry $top.fromsha1 -width 40 -relief flat
7938 $top.fromsha1 insert 0 $oldid
7939 $top.fromsha1 conf -state readonly
7940 grid $top.from $top.fromsha1 -sticky w
7941 entry $top.fromhead -width 60 -relief flat
7942 $top.fromhead insert 0 $oldhead
7943 $top.fromhead conf -state readonly
7944 grid x $top.fromhead -sticky w
7945 label $top.to -text [mc "To:"]
7946 entry $top.tosha1 -width 40 -relief flat
7947 $top.tosha1 insert 0 $newid
7948 $top.tosha1 conf -state readonly
7949 grid $top.to $top.tosha1 -sticky w
7950 entry $top.tohead -width 60 -relief flat
7951 $top.tohead insert 0 $newhead
7952 $top.tohead conf -state readonly
7953 grid x $top.tohead -sticky w
7954 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7955 grid $top.rev x -pady 10
7956 label $top.flab -text [mc "Output file:"]
7957 entry $top.fname -width 60
7958 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7959 incr patchnum
7960 grid $top.flab $top.fname -sticky w
7961 frame $top.buts
7962 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7963 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7964 bind $top <Key-Return> mkpatchgo
7965 bind $top <Key-Escape> mkpatchcan
7966 grid $top.buts.gen $top.buts.can
7967 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7968 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7969 grid $top.buts - -pady 10 -sticky ew
7970 focus $top.fname
7973 proc mkpatchrev {} {
7974 global patchtop
7976 set oldid [$patchtop.fromsha1 get]
7977 set oldhead [$patchtop.fromhead get]
7978 set newid [$patchtop.tosha1 get]
7979 set newhead [$patchtop.tohead get]
7980 foreach e [list fromsha1 fromhead tosha1 tohead] \
7981 v [list $newid $newhead $oldid $oldhead] {
7982 $patchtop.$e conf -state normal
7983 $patchtop.$e delete 0 end
7984 $patchtop.$e insert 0 $v
7985 $patchtop.$e conf -state readonly
7989 proc mkpatchgo {} {
7990 global patchtop nullid nullid2
7992 set oldid [$patchtop.fromsha1 get]
7993 set newid [$patchtop.tosha1 get]
7994 set fname [$patchtop.fname get]
7995 set cmd [diffcmd [list $oldid $newid] -p]
7996 # trim off the initial "|"
7997 set cmd [lrange $cmd 1 end]
7998 lappend cmd >$fname &
7999 if {[catch {eval exec $cmd} err]} {
8000 error_popup "[mc "Error creating patch:"] $err" $patchtop
8002 catch {destroy $patchtop}
8003 unset patchtop
8006 proc mkpatchcan {} {
8007 global patchtop
8009 catch {destroy $patchtop}
8010 unset patchtop
8013 proc mktag {} {
8014 global rowmenuid mktagtop commitinfo
8016 set top .maketag
8017 set mktagtop $top
8018 catch {destroy $top}
8019 toplevel $top
8020 make_transient $top .
8021 label $top.title -text [mc "Create tag"]
8022 grid $top.title - -pady 10
8023 label $top.id -text [mc "ID:"]
8024 entry $top.sha1 -width 40 -relief flat
8025 $top.sha1 insert 0 $rowmenuid
8026 $top.sha1 conf -state readonly
8027 grid $top.id $top.sha1 -sticky w
8028 entry $top.head -width 60 -relief flat
8029 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8030 $top.head conf -state readonly
8031 grid x $top.head -sticky w
8032 label $top.tlab -text [mc "Tag name:"]
8033 entry $top.tag -width 60
8034 grid $top.tlab $top.tag -sticky w
8035 frame $top.buts
8036 button $top.buts.gen -text [mc "Create"] -command mktaggo
8037 button $top.buts.can -text [mc "Cancel"] -command mktagcan
8038 bind $top <Key-Return> mktaggo
8039 bind $top <Key-Escape> mktagcan
8040 grid $top.buts.gen $top.buts.can
8041 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8042 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8043 grid $top.buts - -pady 10 -sticky ew
8044 focus $top.tag
8047 proc domktag {} {
8048 global mktagtop env tagids idtags
8050 set id [$mktagtop.sha1 get]
8051 set tag [$mktagtop.tag get]
8052 if {$tag == {}} {
8053 error_popup [mc "No tag name specified"] $mktagtop
8054 return 0
8056 if {[info exists tagids($tag)]} {
8057 error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
8058 return 0
8060 if {[catch {
8061 exec git tag $tag $id
8062 } err]} {
8063 error_popup "[mc "Error creating tag:"] $err" $mktagtop
8064 return 0
8067 set tagids($tag) $id
8068 lappend idtags($id) $tag
8069 redrawtags $id
8070 addedtag $id
8071 dispneartags 0
8072 run refill_reflist
8073 return 1
8076 proc redrawtags {id} {
8077 global canv linehtag idpos currentid curview cmitlisted
8078 global canvxmax iddrawn circleitem mainheadid circlecolors
8080 if {![commitinview $id $curview]} return
8081 if {![info exists iddrawn($id)]} return
8082 set row [rowofcommit $id]
8083 if {$id eq $mainheadid} {
8084 set ofill yellow
8085 } else {
8086 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
8088 $canv itemconf $circleitem($row) -fill $ofill
8089 $canv delete tag.$id
8090 set xt [eval drawtags $id $idpos($id)]
8091 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
8092 set text [$canv itemcget $linehtag($row) -text]
8093 set font [$canv itemcget $linehtag($row) -font]
8094 set xr [expr {$xt + [font measure $font $text]}]
8095 if {$xr > $canvxmax} {
8096 set canvxmax $xr
8097 setcanvscroll
8099 if {[info exists currentid] && $currentid == $id} {
8100 make_secsel $row
8104 proc mktagcan {} {
8105 global mktagtop
8107 catch {destroy $mktagtop}
8108 unset mktagtop
8111 proc mktaggo {} {
8112 if {![domktag]} return
8113 mktagcan
8116 proc writecommit {} {
8117 global rowmenuid wrcomtop commitinfo wrcomcmd
8119 set top .writecommit
8120 set wrcomtop $top
8121 catch {destroy $top}
8122 toplevel $top
8123 make_transient $top .
8124 label $top.title -text [mc "Write commit to file"]
8125 grid $top.title - -pady 10
8126 label $top.id -text [mc "ID:"]
8127 entry $top.sha1 -width 40 -relief flat
8128 $top.sha1 insert 0 $rowmenuid
8129 $top.sha1 conf -state readonly
8130 grid $top.id $top.sha1 -sticky w
8131 entry $top.head -width 60 -relief flat
8132 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8133 $top.head conf -state readonly
8134 grid x $top.head -sticky w
8135 label $top.clab -text [mc "Command:"]
8136 entry $top.cmd -width 60 -textvariable wrcomcmd
8137 grid $top.clab $top.cmd -sticky w -pady 10
8138 label $top.flab -text [mc "Output file:"]
8139 entry $top.fname -width 60
8140 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
8141 grid $top.flab $top.fname -sticky w
8142 frame $top.buts
8143 button $top.buts.gen -text [mc "Write"] -command wrcomgo
8144 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
8145 bind $top <Key-Return> wrcomgo
8146 bind $top <Key-Escape> wrcomcan
8147 grid $top.buts.gen $top.buts.can
8148 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8149 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8150 grid $top.buts - -pady 10 -sticky ew
8151 focus $top.fname
8154 proc wrcomgo {} {
8155 global wrcomtop
8157 set id [$wrcomtop.sha1 get]
8158 set cmd "echo $id | [$wrcomtop.cmd get]"
8159 set fname [$wrcomtop.fname get]
8160 if {[catch {exec sh -c $cmd >$fname &} err]} {
8161 error_popup "[mc "Error writing commit:"] $err" $wrcomtop
8163 catch {destroy $wrcomtop}
8164 unset wrcomtop
8167 proc wrcomcan {} {
8168 global wrcomtop
8170 catch {destroy $wrcomtop}
8171 unset wrcomtop
8174 proc mkbranch {} {
8175 global rowmenuid mkbrtop
8177 set top .makebranch
8178 catch {destroy $top}
8179 toplevel $top
8180 make_transient $top .
8181 label $top.title -text [mc "Create new branch"]
8182 grid $top.title - -pady 10
8183 label $top.id -text [mc "ID:"]
8184 entry $top.sha1 -width 40 -relief flat
8185 $top.sha1 insert 0 $rowmenuid
8186 $top.sha1 conf -state readonly
8187 grid $top.id $top.sha1 -sticky w
8188 label $top.nlab -text [mc "Name:"]
8189 entry $top.name -width 40
8190 bind $top.name <Key-Return> "[list mkbrgo $top]"
8191 grid $top.nlab $top.name -sticky w
8192 frame $top.buts
8193 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
8194 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
8195 bind $top <Key-Return> [list mkbrgo $top]
8196 bind $top <Key-Escape> "catch {destroy $top}"
8197 grid $top.buts.go $top.buts.can
8198 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8199 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8200 grid $top.buts - -pady 10 -sticky ew
8201 focus $top.name
8204 proc mkbrgo {top} {
8205 global headids idheads
8207 set name [$top.name get]
8208 set id [$top.sha1 get]
8209 set cmdargs {}
8210 set old_id {}
8211 if {$name eq {}} {
8212 error_popup [mc "Please specify a name for the new branch"] $top
8213 return
8215 if {[info exists headids($name)]} {
8216 if {![confirm_popup [mc \
8217 "Branch '%s' already exists. Overwrite?" $name] $top]} {
8218 return
8220 set old_id $headids($name)
8221 lappend cmdargs -f
8223 catch {destroy $top}
8224 lappend cmdargs $name $id
8225 nowbusy newbranch
8226 update
8227 if {[catch {
8228 eval exec git branch $cmdargs
8229 } err]} {
8230 notbusy newbranch
8231 error_popup $err
8232 } else {
8233 notbusy newbranch
8234 if {$old_id ne {}} {
8235 movehead $id $name
8236 movedhead $id $name
8237 redrawtags $old_id
8238 redrawtags $id
8239 } else {
8240 set headids($name) $id
8241 lappend idheads($id) $name
8242 addedhead $id $name
8243 redrawtags $id
8245 dispneartags 0
8246 run refill_reflist
8250 proc exec_citool {tool_args {baseid {}}} {
8251 global commitinfo env
8253 set save_env [array get env GIT_AUTHOR_*]
8255 if {$baseid ne {}} {
8256 if {![info exists commitinfo($baseid)]} {
8257 getcommit $baseid
8259 set author [lindex $commitinfo($baseid) 1]
8260 set date [lindex $commitinfo($baseid) 2]
8261 if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
8262 $author author name email]
8263 && $date ne {}} {
8264 set env(GIT_AUTHOR_NAME) $name
8265 set env(GIT_AUTHOR_EMAIL) $email
8266 set env(GIT_AUTHOR_DATE) $date
8270 eval exec git citool $tool_args &
8272 array unset env GIT_AUTHOR_*
8273 array set env $save_env
8276 proc cherrypick {} {
8277 global rowmenuid curview
8278 global mainhead mainheadid
8280 set oldhead [exec git rev-parse HEAD]
8281 set dheads [descheads $rowmenuid]
8282 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
8283 set ok [confirm_popup [mc "Commit %s is already\
8284 included in branch %s -- really re-apply it?" \
8285 [string range $rowmenuid 0 7] $mainhead]]
8286 if {!$ok} return
8288 nowbusy cherrypick [mc "Cherry-picking"]
8289 update
8290 # Unfortunately git-cherry-pick writes stuff to stderr even when
8291 # no error occurs, and exec takes that as an indication of error...
8292 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
8293 notbusy cherrypick
8294 if {[regexp -line \
8295 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
8296 $err msg fname]} {
8297 error_popup [mc "Cherry-pick failed because of local changes\
8298 to file '%s'.\nPlease commit, reset or stash\
8299 your changes and try again." $fname]
8300 } elseif {[regexp -line \
8301 {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \
8302 $err]} {
8303 if {[confirm_popup [mc "Cherry-pick failed because of merge\
8304 conflict.\nDo you wish to run git citool to\
8305 resolve it?"]]} {
8306 # Force citool to read MERGE_MSG
8307 file delete [file join [gitdir] "GITGUI_MSG"]
8308 exec_citool {} $rowmenuid
8310 } else {
8311 error_popup $err
8313 run updatecommits
8314 return
8316 set newhead [exec git rev-parse HEAD]
8317 if {$newhead eq $oldhead} {
8318 notbusy cherrypick
8319 error_popup [mc "No changes committed"]
8320 return
8322 addnewchild $newhead $oldhead
8323 if {[commitinview $oldhead $curview]} {
8324 insertrow $newhead $oldhead $curview
8325 if {$mainhead ne {}} {
8326 movehead $newhead $mainhead
8327 movedhead $newhead $mainhead
8329 set mainheadid $newhead
8330 redrawtags $oldhead
8331 redrawtags $newhead
8332 selbyid $newhead
8334 notbusy cherrypick
8337 proc resethead {} {
8338 global mainhead rowmenuid confirm_ok resettype
8340 set confirm_ok 0
8341 set w ".confirmreset"
8342 toplevel $w
8343 make_transient $w .
8344 wm title $w [mc "Confirm reset"]
8345 message $w.m -text \
8346 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
8347 -justify center -aspect 1000
8348 pack $w.m -side top -fill x -padx 20 -pady 20
8349 frame $w.f -relief sunken -border 2
8350 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
8351 grid $w.f.rt -sticky w
8352 set resettype mixed
8353 radiobutton $w.f.soft -value soft -variable resettype -justify left \
8354 -text [mc "Soft: Leave working tree and index untouched"]
8355 grid $w.f.soft -sticky w
8356 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
8357 -text [mc "Mixed: Leave working tree untouched, reset index"]
8358 grid $w.f.mixed -sticky w
8359 radiobutton $w.f.hard -value hard -variable resettype -justify left \
8360 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
8361 grid $w.f.hard -sticky w
8362 pack $w.f -side top -fill x
8363 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
8364 pack $w.ok -side left -fill x -padx 20 -pady 20
8365 button $w.cancel -text [mc Cancel] -command "destroy $w"
8366 bind $w <Key-Escape> [list destroy $w]
8367 pack $w.cancel -side right -fill x -padx 20 -pady 20
8368 bind $w <Visibility> "grab $w; focus $w"
8369 tkwait window $w
8370 if {!$confirm_ok} return
8371 if {[catch {set fd [open \
8372 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
8373 error_popup $err
8374 } else {
8375 dohidelocalchanges
8376 filerun $fd [list readresetstat $fd]
8377 nowbusy reset [mc "Resetting"]
8378 selbyid $rowmenuid
8382 proc readresetstat {fd} {
8383 global mainhead mainheadid showlocalchanges rprogcoord
8385 if {[gets $fd line] >= 0} {
8386 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8387 set rprogcoord [expr {1.0 * $m / $n}]
8388 adjustprogress
8390 return 1
8392 set rprogcoord 0
8393 adjustprogress
8394 notbusy reset
8395 if {[catch {close $fd} err]} {
8396 error_popup $err
8398 set oldhead $mainheadid
8399 set newhead [exec git rev-parse HEAD]
8400 if {$newhead ne $oldhead} {
8401 movehead $newhead $mainhead
8402 movedhead $newhead $mainhead
8403 set mainheadid $newhead
8404 redrawtags $oldhead
8405 redrawtags $newhead
8407 if {$showlocalchanges} {
8408 doshowlocalchanges
8410 return 0
8413 # context menu for a head
8414 proc headmenu {x y id head} {
8415 global headmenuid headmenuhead headctxmenu mainhead
8417 stopfinding
8418 set headmenuid $id
8419 set headmenuhead $head
8420 set state normal
8421 if {$head eq $mainhead} {
8422 set state disabled
8424 $headctxmenu entryconfigure 0 -state $state
8425 $headctxmenu entryconfigure 1 -state $state
8426 tk_popup $headctxmenu $x $y
8429 proc cobranch {} {
8430 global headmenuid headmenuhead headids
8431 global showlocalchanges mainheadid
8433 # check the tree is clean first??
8434 nowbusy checkout [mc "Checking out"]
8435 update
8436 dohidelocalchanges
8437 if {[catch {
8438 set fd [open [list | git checkout $headmenuhead 2>@1] r]
8439 } err]} {
8440 notbusy checkout
8441 error_popup $err
8442 if {$showlocalchanges} {
8443 dodiffindex
8445 } else {
8446 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
8450 proc readcheckoutstat {fd newhead newheadid} {
8451 global mainhead mainheadid headids showlocalchanges progresscoords
8453 if {[gets $fd line] >= 0} {
8454 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8455 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
8456 adjustprogress
8458 return 1
8460 set progresscoords {0 0}
8461 adjustprogress
8462 notbusy checkout
8463 if {[catch {close $fd} err]} {
8464 error_popup $err
8466 set oldmainid $mainheadid
8467 set mainhead $newhead
8468 set mainheadid $newheadid
8469 redrawtags $oldmainid
8470 redrawtags $newheadid
8471 selbyid $newheadid
8472 if {$showlocalchanges} {
8473 dodiffindex
8477 proc rmbranch {} {
8478 global headmenuid headmenuhead mainhead
8479 global idheads
8481 set head $headmenuhead
8482 set id $headmenuid
8483 # this check shouldn't be needed any more...
8484 if {$head eq $mainhead} {
8485 error_popup [mc "Cannot delete the currently checked-out branch"]
8486 return
8488 set dheads [descheads $id]
8489 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
8490 # the stuff on this branch isn't on any other branch
8491 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
8492 branch.\nReally delete branch %s?" $head $head]]} return
8494 nowbusy rmbranch
8495 update
8496 if {[catch {exec git branch -D $head} err]} {
8497 notbusy rmbranch
8498 error_popup $err
8499 return
8501 removehead $id $head
8502 removedhead $id $head
8503 redrawtags $id
8504 notbusy rmbranch
8505 dispneartags 0
8506 run refill_reflist
8509 # Display a list of tags and heads
8510 proc showrefs {} {
8511 global showrefstop bgcolor fgcolor selectbgcolor
8512 global bglist fglist reflistfilter reflist maincursor
8514 set top .showrefs
8515 set showrefstop $top
8516 if {[winfo exists $top]} {
8517 raise $top
8518 refill_reflist
8519 return
8521 toplevel $top
8522 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
8523 make_transient $top .
8524 text $top.list -background $bgcolor -foreground $fgcolor \
8525 -selectbackground $selectbgcolor -font mainfont \
8526 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
8527 -width 30 -height 20 -cursor $maincursor \
8528 -spacing1 1 -spacing3 1 -state disabled
8529 $top.list tag configure highlight -background $selectbgcolor
8530 lappend bglist $top.list
8531 lappend fglist $top.list
8532 scrollbar $top.ysb -command "$top.list yview" -orient vertical
8533 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
8534 grid $top.list $top.ysb -sticky nsew
8535 grid $top.xsb x -sticky ew
8536 frame $top.f
8537 label $top.f.l -text "[mc "Filter"]: "
8538 entry $top.f.e -width 20 -textvariable reflistfilter
8539 set reflistfilter "*"
8540 trace add variable reflistfilter write reflistfilter_change
8541 pack $top.f.e -side right -fill x -expand 1
8542 pack $top.f.l -side left
8543 grid $top.f - -sticky ew -pady 2
8544 button $top.close -command [list destroy $top] -text [mc "Close"]
8545 bind $top <Key-Escape> [list destroy $top]
8546 grid $top.close -
8547 grid columnconfigure $top 0 -weight 1
8548 grid rowconfigure $top 0 -weight 1
8549 bind $top.list <1> {break}
8550 bind $top.list <B1-Motion> {break}
8551 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
8552 set reflist {}
8553 refill_reflist
8556 proc sel_reflist {w x y} {
8557 global showrefstop reflist headids tagids otherrefids
8559 if {![winfo exists $showrefstop]} return
8560 set l [lindex [split [$w index "@$x,$y"] "."] 0]
8561 set ref [lindex $reflist [expr {$l-1}]]
8562 set n [lindex $ref 0]
8563 switch -- [lindex $ref 1] {
8564 "H" {selbyid $headids($n)}
8565 "T" {selbyid $tagids($n)}
8566 "o" {selbyid $otherrefids($n)}
8568 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
8571 proc unsel_reflist {} {
8572 global showrefstop
8574 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8575 $showrefstop.list tag remove highlight 0.0 end
8578 proc reflistfilter_change {n1 n2 op} {
8579 global reflistfilter
8581 after cancel refill_reflist
8582 after 200 refill_reflist
8585 proc refill_reflist {} {
8586 global reflist reflistfilter showrefstop headids tagids otherrefids
8587 global curview
8589 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8590 set refs {}
8591 foreach n [array names headids] {
8592 if {[string match $reflistfilter $n]} {
8593 if {[commitinview $headids($n) $curview]} {
8594 lappend refs [list $n H]
8595 } else {
8596 interestedin $headids($n) {run refill_reflist}
8600 foreach n [array names tagids] {
8601 if {[string match $reflistfilter $n]} {
8602 if {[commitinview $tagids($n) $curview]} {
8603 lappend refs [list $n T]
8604 } else {
8605 interestedin $tagids($n) {run refill_reflist}
8609 foreach n [array names otherrefids] {
8610 if {[string match $reflistfilter $n]} {
8611 if {[commitinview $otherrefids($n) $curview]} {
8612 lappend refs [list $n o]
8613 } else {
8614 interestedin $otherrefids($n) {run refill_reflist}
8618 set refs [lsort -index 0 $refs]
8619 if {$refs eq $reflist} return
8621 # Update the contents of $showrefstop.list according to the
8622 # differences between $reflist (old) and $refs (new)
8623 $showrefstop.list conf -state normal
8624 $showrefstop.list insert end "\n"
8625 set i 0
8626 set j 0
8627 while {$i < [llength $reflist] || $j < [llength $refs]} {
8628 if {$i < [llength $reflist]} {
8629 if {$j < [llength $refs]} {
8630 set cmp [string compare [lindex $reflist $i 0] \
8631 [lindex $refs $j 0]]
8632 if {$cmp == 0} {
8633 set cmp [string compare [lindex $reflist $i 1] \
8634 [lindex $refs $j 1]]
8636 } else {
8637 set cmp -1
8639 } else {
8640 set cmp 1
8642 switch -- $cmp {
8643 -1 {
8644 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
8645 incr i
8648 incr i
8649 incr j
8652 set l [expr {$j + 1}]
8653 $showrefstop.list image create $l.0 -align baseline \
8654 -image reficon-[lindex $refs $j 1] -padx 2
8655 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
8656 incr j
8660 set reflist $refs
8661 # delete last newline
8662 $showrefstop.list delete end-2c end-1c
8663 $showrefstop.list conf -state disabled
8666 # Stuff for finding nearby tags
8667 proc getallcommits {} {
8668 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
8669 global idheads idtags idotherrefs allparents tagobjid
8671 if {![info exists allcommits]} {
8672 set nextarc 0
8673 set allcommits 0
8674 set seeds {}
8675 set allcwait 0
8676 set cachedarcs 0
8677 set allccache [file join [gitdir] "gitk.cache"]
8678 if {![catch {
8679 set f [open $allccache r]
8680 set allcwait 1
8681 getcache $f
8682 }]} return
8685 if {$allcwait} {
8686 return
8688 set cmd [list | git rev-list --parents]
8689 set allcupdate [expr {$seeds ne {}}]
8690 if {!$allcupdate} {
8691 set ids "--all"
8692 } else {
8693 set refs [concat [array names idheads] [array names idtags] \
8694 [array names idotherrefs]]
8695 set ids {}
8696 set tagobjs {}
8697 foreach name [array names tagobjid] {
8698 lappend tagobjs $tagobjid($name)
8700 foreach id [lsort -unique $refs] {
8701 if {![info exists allparents($id)] &&
8702 [lsearch -exact $tagobjs $id] < 0} {
8703 lappend ids $id
8706 if {$ids ne {}} {
8707 foreach id $seeds {
8708 lappend ids "^$id"
8712 if {$ids ne {}} {
8713 set fd [open [concat $cmd $ids] r]
8714 fconfigure $fd -blocking 0
8715 incr allcommits
8716 nowbusy allcommits
8717 filerun $fd [list getallclines $fd]
8718 } else {
8719 dispneartags 0
8723 # Since most commits have 1 parent and 1 child, we group strings of
8724 # such commits into "arcs" joining branch/merge points (BMPs), which
8725 # are commits that either don't have 1 parent or don't have 1 child.
8727 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8728 # arcout(id) - outgoing arcs for BMP
8729 # arcids(a) - list of IDs on arc including end but not start
8730 # arcstart(a) - BMP ID at start of arc
8731 # arcend(a) - BMP ID at end of arc
8732 # growing(a) - arc a is still growing
8733 # arctags(a) - IDs out of arcids (excluding end) that have tags
8734 # archeads(a) - IDs out of arcids (excluding end) that have heads
8735 # The start of an arc is at the descendent end, so "incoming" means
8736 # coming from descendents, and "outgoing" means going towards ancestors.
8738 proc getallclines {fd} {
8739 global allparents allchildren idtags idheads nextarc
8740 global arcnos arcids arctags arcout arcend arcstart archeads growing
8741 global seeds allcommits cachedarcs allcupdate
8743 set nid 0
8744 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8745 set id [lindex $line 0]
8746 if {[info exists allparents($id)]} {
8747 # seen it already
8748 continue
8750 set cachedarcs 0
8751 set olds [lrange $line 1 end]
8752 set allparents($id) $olds
8753 if {![info exists allchildren($id)]} {
8754 set allchildren($id) {}
8755 set arcnos($id) {}
8756 lappend seeds $id
8757 } else {
8758 set a $arcnos($id)
8759 if {[llength $olds] == 1 && [llength $a] == 1} {
8760 lappend arcids($a) $id
8761 if {[info exists idtags($id)]} {
8762 lappend arctags($a) $id
8764 if {[info exists idheads($id)]} {
8765 lappend archeads($a) $id
8767 if {[info exists allparents($olds)]} {
8768 # seen parent already
8769 if {![info exists arcout($olds)]} {
8770 splitarc $olds
8772 lappend arcids($a) $olds
8773 set arcend($a) $olds
8774 unset growing($a)
8776 lappend allchildren($olds) $id
8777 lappend arcnos($olds) $a
8778 continue
8781 foreach a $arcnos($id) {
8782 lappend arcids($a) $id
8783 set arcend($a) $id
8784 unset growing($a)
8787 set ao {}
8788 foreach p $olds {
8789 lappend allchildren($p) $id
8790 set a [incr nextarc]
8791 set arcstart($a) $id
8792 set archeads($a) {}
8793 set arctags($a) {}
8794 set archeads($a) {}
8795 set arcids($a) {}
8796 lappend ao $a
8797 set growing($a) 1
8798 if {[info exists allparents($p)]} {
8799 # seen it already, may need to make a new branch
8800 if {![info exists arcout($p)]} {
8801 splitarc $p
8803 lappend arcids($a) $p
8804 set arcend($a) $p
8805 unset growing($a)
8807 lappend arcnos($p) $a
8809 set arcout($id) $ao
8811 if {$nid > 0} {
8812 global cached_dheads cached_dtags cached_atags
8813 catch {unset cached_dheads}
8814 catch {unset cached_dtags}
8815 catch {unset cached_atags}
8817 if {![eof $fd]} {
8818 return [expr {$nid >= 1000? 2: 1}]
8820 set cacheok 1
8821 if {[catch {
8822 fconfigure $fd -blocking 1
8823 close $fd
8824 } err]} {
8825 # got an error reading the list of commits
8826 # if we were updating, try rereading the whole thing again
8827 if {$allcupdate} {
8828 incr allcommits -1
8829 dropcache $err
8830 return
8832 error_popup "[mc "Error reading commit topology information;\
8833 branch and preceding/following tag information\
8834 will be incomplete."]\n($err)"
8835 set cacheok 0
8837 if {[incr allcommits -1] == 0} {
8838 notbusy allcommits
8839 if {$cacheok} {
8840 run savecache
8843 dispneartags 0
8844 return 0
8847 proc recalcarc {a} {
8848 global arctags archeads arcids idtags idheads
8850 set at {}
8851 set ah {}
8852 foreach id [lrange $arcids($a) 0 end-1] {
8853 if {[info exists idtags($id)]} {
8854 lappend at $id
8856 if {[info exists idheads($id)]} {
8857 lappend ah $id
8860 set arctags($a) $at
8861 set archeads($a) $ah
8864 proc splitarc {p} {
8865 global arcnos arcids nextarc arctags archeads idtags idheads
8866 global arcstart arcend arcout allparents growing
8868 set a $arcnos($p)
8869 if {[llength $a] != 1} {
8870 puts "oops splitarc called but [llength $a] arcs already"
8871 return
8873 set a [lindex $a 0]
8874 set i [lsearch -exact $arcids($a) $p]
8875 if {$i < 0} {
8876 puts "oops splitarc $p not in arc $a"
8877 return
8879 set na [incr nextarc]
8880 if {[info exists arcend($a)]} {
8881 set arcend($na) $arcend($a)
8882 } else {
8883 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8884 set j [lsearch -exact $arcnos($l) $a]
8885 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8887 set tail [lrange $arcids($a) [expr {$i+1}] end]
8888 set arcids($a) [lrange $arcids($a) 0 $i]
8889 set arcend($a) $p
8890 set arcstart($na) $p
8891 set arcout($p) $na
8892 set arcids($na) $tail
8893 if {[info exists growing($a)]} {
8894 set growing($na) 1
8895 unset growing($a)
8898 foreach id $tail {
8899 if {[llength $arcnos($id)] == 1} {
8900 set arcnos($id) $na
8901 } else {
8902 set j [lsearch -exact $arcnos($id) $a]
8903 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8907 # reconstruct tags and heads lists
8908 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8909 recalcarc $a
8910 recalcarc $na
8911 } else {
8912 set arctags($na) {}
8913 set archeads($na) {}
8917 # Update things for a new commit added that is a child of one
8918 # existing commit. Used when cherry-picking.
8919 proc addnewchild {id p} {
8920 global allparents allchildren idtags nextarc
8921 global arcnos arcids arctags arcout arcend arcstart archeads growing
8922 global seeds allcommits
8924 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8925 set allparents($id) [list $p]
8926 set allchildren($id) {}
8927 set arcnos($id) {}
8928 lappend seeds $id
8929 lappend allchildren($p) $id
8930 set a [incr nextarc]
8931 set arcstart($a) $id
8932 set archeads($a) {}
8933 set arctags($a) {}
8934 set arcids($a) [list $p]
8935 set arcend($a) $p
8936 if {![info exists arcout($p)]} {
8937 splitarc $p
8939 lappend arcnos($p) $a
8940 set arcout($id) [list $a]
8943 # This implements a cache for the topology information.
8944 # The cache saves, for each arc, the start and end of the arc,
8945 # the ids on the arc, and the outgoing arcs from the end.
8946 proc readcache {f} {
8947 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8948 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8949 global allcwait
8951 set a $nextarc
8952 set lim $cachedarcs
8953 if {$lim - $a > 500} {
8954 set lim [expr {$a + 500}]
8956 if {[catch {
8957 if {$a == $lim} {
8958 # finish reading the cache and setting up arctags, etc.
8959 set line [gets $f]
8960 if {$line ne "1"} {error "bad final version"}
8961 close $f
8962 foreach id [array names idtags] {
8963 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8964 [llength $allparents($id)] == 1} {
8965 set a [lindex $arcnos($id) 0]
8966 if {$arctags($a) eq {}} {
8967 recalcarc $a
8971 foreach id [array names idheads] {
8972 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8973 [llength $allparents($id)] == 1} {
8974 set a [lindex $arcnos($id) 0]
8975 if {$archeads($a) eq {}} {
8976 recalcarc $a
8980 foreach id [lsort -unique $possible_seeds] {
8981 if {$arcnos($id) eq {}} {
8982 lappend seeds $id
8985 set allcwait 0
8986 } else {
8987 while {[incr a] <= $lim} {
8988 set line [gets $f]
8989 if {[llength $line] != 3} {error "bad line"}
8990 set s [lindex $line 0]
8991 set arcstart($a) $s
8992 lappend arcout($s) $a
8993 if {![info exists arcnos($s)]} {
8994 lappend possible_seeds $s
8995 set arcnos($s) {}
8997 set e [lindex $line 1]
8998 if {$e eq {}} {
8999 set growing($a) 1
9000 } else {
9001 set arcend($a) $e
9002 if {![info exists arcout($e)]} {
9003 set arcout($e) {}
9006 set arcids($a) [lindex $line 2]
9007 foreach id $arcids($a) {
9008 lappend allparents($s) $id
9009 set s $id
9010 lappend arcnos($id) $a
9012 if {![info exists allparents($s)]} {
9013 set allparents($s) {}
9015 set arctags($a) {}
9016 set archeads($a) {}
9018 set nextarc [expr {$a - 1}]
9020 } err]} {
9021 dropcache $err
9022 return 0
9024 if {!$allcwait} {
9025 getallcommits
9027 return $allcwait
9030 proc getcache {f} {
9031 global nextarc cachedarcs possible_seeds
9033 if {[catch {
9034 set line [gets $f]
9035 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
9036 # make sure it's an integer
9037 set cachedarcs [expr {int([lindex $line 1])}]
9038 if {$cachedarcs < 0} {error "bad number of arcs"}
9039 set nextarc 0
9040 set possible_seeds {}
9041 run readcache $f
9042 } err]} {
9043 dropcache $err
9045 return 0
9048 proc dropcache {err} {
9049 global allcwait nextarc cachedarcs seeds
9051 #puts "dropping cache ($err)"
9052 foreach v {arcnos arcout arcids arcstart arcend growing \
9053 arctags archeads allparents allchildren} {
9054 global $v
9055 catch {unset $v}
9057 set allcwait 0
9058 set nextarc 0
9059 set cachedarcs 0
9060 set seeds {}
9061 getallcommits
9064 proc writecache {f} {
9065 global cachearc cachedarcs allccache
9066 global arcstart arcend arcnos arcids arcout
9068 set a $cachearc
9069 set lim $cachedarcs
9070 if {$lim - $a > 1000} {
9071 set lim [expr {$a + 1000}]
9073 if {[catch {
9074 while {[incr a] <= $lim} {
9075 if {[info exists arcend($a)]} {
9076 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
9077 } else {
9078 puts $f [list $arcstart($a) {} $arcids($a)]
9081 } err]} {
9082 catch {close $f}
9083 catch {file delete $allccache}
9084 #puts "writing cache failed ($err)"
9085 return 0
9087 set cachearc [expr {$a - 1}]
9088 if {$a > $cachedarcs} {
9089 puts $f "1"
9090 close $f
9091 return 0
9093 return 1
9096 proc savecache {} {
9097 global nextarc cachedarcs cachearc allccache
9099 if {$nextarc == $cachedarcs} return
9100 set cachearc 0
9101 set cachedarcs $nextarc
9102 catch {
9103 set f [open $allccache w]
9104 puts $f [list 1 $cachedarcs]
9105 run writecache $f
9109 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
9110 # or 0 if neither is true.
9111 proc anc_or_desc {a b} {
9112 global arcout arcstart arcend arcnos cached_isanc
9114 if {$arcnos($a) eq $arcnos($b)} {
9115 # Both are on the same arc(s); either both are the same BMP,
9116 # or if one is not a BMP, the other is also not a BMP or is
9117 # the BMP at end of the arc (and it only has 1 incoming arc).
9118 # Or both can be BMPs with no incoming arcs.
9119 if {$a eq $b || $arcnos($a) eq {}} {
9120 return 0
9122 # assert {[llength $arcnos($a)] == 1}
9123 set arc [lindex $arcnos($a) 0]
9124 set i [lsearch -exact $arcids($arc) $a]
9125 set j [lsearch -exact $arcids($arc) $b]
9126 if {$i < 0 || $i > $j} {
9127 return 1
9128 } else {
9129 return -1
9133 if {![info exists arcout($a)]} {
9134 set arc [lindex $arcnos($a) 0]
9135 if {[info exists arcend($arc)]} {
9136 set aend $arcend($arc)
9137 } else {
9138 set aend {}
9140 set a $arcstart($arc)
9141 } else {
9142 set aend $a
9144 if {![info exists arcout($b)]} {
9145 set arc [lindex $arcnos($b) 0]
9146 if {[info exists arcend($arc)]} {
9147 set bend $arcend($arc)
9148 } else {
9149 set bend {}
9151 set b $arcstart($arc)
9152 } else {
9153 set bend $b
9155 if {$a eq $bend} {
9156 return 1
9158 if {$b eq $aend} {
9159 return -1
9161 if {[info exists cached_isanc($a,$bend)]} {
9162 if {$cached_isanc($a,$bend)} {
9163 return 1
9166 if {[info exists cached_isanc($b,$aend)]} {
9167 if {$cached_isanc($b,$aend)} {
9168 return -1
9170 if {[info exists cached_isanc($a,$bend)]} {
9171 return 0
9175 set todo [list $a $b]
9176 set anc($a) a
9177 set anc($b) b
9178 for {set i 0} {$i < [llength $todo]} {incr i} {
9179 set x [lindex $todo $i]
9180 if {$anc($x) eq {}} {
9181 continue
9183 foreach arc $arcnos($x) {
9184 set xd $arcstart($arc)
9185 if {$xd eq $bend} {
9186 set cached_isanc($a,$bend) 1
9187 set cached_isanc($b,$aend) 0
9188 return 1
9189 } elseif {$xd eq $aend} {
9190 set cached_isanc($b,$aend) 1
9191 set cached_isanc($a,$bend) 0
9192 return -1
9194 if {![info exists anc($xd)]} {
9195 set anc($xd) $anc($x)
9196 lappend todo $xd
9197 } elseif {$anc($xd) ne $anc($x)} {
9198 set anc($xd) {}
9202 set cached_isanc($a,$bend) 0
9203 set cached_isanc($b,$aend) 0
9204 return 0
9207 # This identifies whether $desc has an ancestor that is
9208 # a growing tip of the graph and which is not an ancestor of $anc
9209 # and returns 0 if so and 1 if not.
9210 # If we subsequently discover a tag on such a growing tip, and that
9211 # turns out to be a descendent of $anc (which it could, since we
9212 # don't necessarily see children before parents), then $desc
9213 # isn't a good choice to display as a descendent tag of
9214 # $anc (since it is the descendent of another tag which is
9215 # a descendent of $anc). Similarly, $anc isn't a good choice to
9216 # display as a ancestor tag of $desc.
9218 proc is_certain {desc anc} {
9219 global arcnos arcout arcstart arcend growing problems
9221 set certain {}
9222 if {[llength $arcnos($anc)] == 1} {
9223 # tags on the same arc are certain
9224 if {$arcnos($desc) eq $arcnos($anc)} {
9225 return 1
9227 if {![info exists arcout($anc)]} {
9228 # if $anc is partway along an arc, use the start of the arc instead
9229 set a [lindex $arcnos($anc) 0]
9230 set anc $arcstart($a)
9233 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
9234 set x $desc
9235 } else {
9236 set a [lindex $arcnos($desc) 0]
9237 set x $arcend($a)
9239 if {$x == $anc} {
9240 return 1
9242 set anclist [list $x]
9243 set dl($x) 1
9244 set nnh 1
9245 set ngrowanc 0
9246 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
9247 set x [lindex $anclist $i]
9248 if {$dl($x)} {
9249 incr nnh -1
9251 set done($x) 1
9252 foreach a $arcout($x) {
9253 if {[info exists growing($a)]} {
9254 if {![info exists growanc($x)] && $dl($x)} {
9255 set growanc($x) 1
9256 incr ngrowanc
9258 } else {
9259 set y $arcend($a)
9260 if {[info exists dl($y)]} {
9261 if {$dl($y)} {
9262 if {!$dl($x)} {
9263 set dl($y) 0
9264 if {![info exists done($y)]} {
9265 incr nnh -1
9267 if {[info exists growanc($x)]} {
9268 incr ngrowanc -1
9270 set xl [list $y]
9271 for {set k 0} {$k < [llength $xl]} {incr k} {
9272 set z [lindex $xl $k]
9273 foreach c $arcout($z) {
9274 if {[info exists arcend($c)]} {
9275 set v $arcend($c)
9276 if {[info exists dl($v)] && $dl($v)} {
9277 set dl($v) 0
9278 if {![info exists done($v)]} {
9279 incr nnh -1
9281 if {[info exists growanc($v)]} {
9282 incr ngrowanc -1
9284 lappend xl $v
9291 } elseif {$y eq $anc || !$dl($x)} {
9292 set dl($y) 0
9293 lappend anclist $y
9294 } else {
9295 set dl($y) 1
9296 lappend anclist $y
9297 incr nnh
9302 foreach x [array names growanc] {
9303 if {$dl($x)} {
9304 return 0
9306 return 0
9308 return 1
9311 proc validate_arctags {a} {
9312 global arctags idtags
9314 set i -1
9315 set na $arctags($a)
9316 foreach id $arctags($a) {
9317 incr i
9318 if {![info exists idtags($id)]} {
9319 set na [lreplace $na $i $i]
9320 incr i -1
9323 set arctags($a) $na
9326 proc validate_archeads {a} {
9327 global archeads idheads
9329 set i -1
9330 set na $archeads($a)
9331 foreach id $archeads($a) {
9332 incr i
9333 if {![info exists idheads($id)]} {
9334 set na [lreplace $na $i $i]
9335 incr i -1
9338 set archeads($a) $na
9341 # Return the list of IDs that have tags that are descendents of id,
9342 # ignoring IDs that are descendents of IDs already reported.
9343 proc desctags {id} {
9344 global arcnos arcstart arcids arctags idtags allparents
9345 global growing cached_dtags
9347 if {![info exists allparents($id)]} {
9348 return {}
9350 set t1 [clock clicks -milliseconds]
9351 set argid $id
9352 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9353 # part-way along an arc; check that arc first
9354 set a [lindex $arcnos($id) 0]
9355 if {$arctags($a) ne {}} {
9356 validate_arctags $a
9357 set i [lsearch -exact $arcids($a) $id]
9358 set tid {}
9359 foreach t $arctags($a) {
9360 set j [lsearch -exact $arcids($a) $t]
9361 if {$j >= $i} break
9362 set tid $t
9364 if {$tid ne {}} {
9365 return $tid
9368 set id $arcstart($a)
9369 if {[info exists idtags($id)]} {
9370 return $id
9373 if {[info exists cached_dtags($id)]} {
9374 return $cached_dtags($id)
9377 set origid $id
9378 set todo [list $id]
9379 set queued($id) 1
9380 set nc 1
9381 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9382 set id [lindex $todo $i]
9383 set done($id) 1
9384 set ta [info exists hastaggedancestor($id)]
9385 if {!$ta} {
9386 incr nc -1
9388 # ignore tags on starting node
9389 if {!$ta && $i > 0} {
9390 if {[info exists idtags($id)]} {
9391 set tagloc($id) $id
9392 set ta 1
9393 } elseif {[info exists cached_dtags($id)]} {
9394 set tagloc($id) $cached_dtags($id)
9395 set ta 1
9398 foreach a $arcnos($id) {
9399 set d $arcstart($a)
9400 if {!$ta && $arctags($a) ne {}} {
9401 validate_arctags $a
9402 if {$arctags($a) ne {}} {
9403 lappend tagloc($id) [lindex $arctags($a) end]
9406 if {$ta || $arctags($a) ne {}} {
9407 set tomark [list $d]
9408 for {set j 0} {$j < [llength $tomark]} {incr j} {
9409 set dd [lindex $tomark $j]
9410 if {![info exists hastaggedancestor($dd)]} {
9411 if {[info exists done($dd)]} {
9412 foreach b $arcnos($dd) {
9413 lappend tomark $arcstart($b)
9415 if {[info exists tagloc($dd)]} {
9416 unset tagloc($dd)
9418 } elseif {[info exists queued($dd)]} {
9419 incr nc -1
9421 set hastaggedancestor($dd) 1
9425 if {![info exists queued($d)]} {
9426 lappend todo $d
9427 set queued($d) 1
9428 if {![info exists hastaggedancestor($d)]} {
9429 incr nc
9434 set tags {}
9435 foreach id [array names tagloc] {
9436 if {![info exists hastaggedancestor($id)]} {
9437 foreach t $tagloc($id) {
9438 if {[lsearch -exact $tags $t] < 0} {
9439 lappend tags $t
9444 set t2 [clock clicks -milliseconds]
9445 set loopix $i
9447 # remove tags that are descendents of other tags
9448 for {set i 0} {$i < [llength $tags]} {incr i} {
9449 set a [lindex $tags $i]
9450 for {set j 0} {$j < $i} {incr j} {
9451 set b [lindex $tags $j]
9452 set r [anc_or_desc $a $b]
9453 if {$r == 1} {
9454 set tags [lreplace $tags $j $j]
9455 incr j -1
9456 incr i -1
9457 } elseif {$r == -1} {
9458 set tags [lreplace $tags $i $i]
9459 incr i -1
9460 break
9465 if {[array names growing] ne {}} {
9466 # graph isn't finished, need to check if any tag could get
9467 # eclipsed by another tag coming later. Simply ignore any
9468 # tags that could later get eclipsed.
9469 set ctags {}
9470 foreach t $tags {
9471 if {[is_certain $t $origid]} {
9472 lappend ctags $t
9475 if {$tags eq $ctags} {
9476 set cached_dtags($origid) $tags
9477 } else {
9478 set tags $ctags
9480 } else {
9481 set cached_dtags($origid) $tags
9483 set t3 [clock clicks -milliseconds]
9484 if {0 && $t3 - $t1 >= 100} {
9485 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
9486 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9488 return $tags
9491 proc anctags {id} {
9492 global arcnos arcids arcout arcend arctags idtags allparents
9493 global growing cached_atags
9495 if {![info exists allparents($id)]} {
9496 return {}
9498 set t1 [clock clicks -milliseconds]
9499 set argid $id
9500 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9501 # part-way along an arc; check that arc first
9502 set a [lindex $arcnos($id) 0]
9503 if {$arctags($a) ne {}} {
9504 validate_arctags $a
9505 set i [lsearch -exact $arcids($a) $id]
9506 foreach t $arctags($a) {
9507 set j [lsearch -exact $arcids($a) $t]
9508 if {$j > $i} {
9509 return $t
9513 if {![info exists arcend($a)]} {
9514 return {}
9516 set id $arcend($a)
9517 if {[info exists idtags($id)]} {
9518 return $id
9521 if {[info exists cached_atags($id)]} {
9522 return $cached_atags($id)
9525 set origid $id
9526 set todo [list $id]
9527 set queued($id) 1
9528 set taglist {}
9529 set nc 1
9530 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9531 set id [lindex $todo $i]
9532 set done($id) 1
9533 set td [info exists hastaggeddescendent($id)]
9534 if {!$td} {
9535 incr nc -1
9537 # ignore tags on starting node
9538 if {!$td && $i > 0} {
9539 if {[info exists idtags($id)]} {
9540 set tagloc($id) $id
9541 set td 1
9542 } elseif {[info exists cached_atags($id)]} {
9543 set tagloc($id) $cached_atags($id)
9544 set td 1
9547 foreach a $arcout($id) {
9548 if {!$td && $arctags($a) ne {}} {
9549 validate_arctags $a
9550 if {$arctags($a) ne {}} {
9551 lappend tagloc($id) [lindex $arctags($a) 0]
9554 if {![info exists arcend($a)]} continue
9555 set d $arcend($a)
9556 if {$td || $arctags($a) ne {}} {
9557 set tomark [list $d]
9558 for {set j 0} {$j < [llength $tomark]} {incr j} {
9559 set dd [lindex $tomark $j]
9560 if {![info exists hastaggeddescendent($dd)]} {
9561 if {[info exists done($dd)]} {
9562 foreach b $arcout($dd) {
9563 if {[info exists arcend($b)]} {
9564 lappend tomark $arcend($b)
9567 if {[info exists tagloc($dd)]} {
9568 unset tagloc($dd)
9570 } elseif {[info exists queued($dd)]} {
9571 incr nc -1
9573 set hastaggeddescendent($dd) 1
9577 if {![info exists queued($d)]} {
9578 lappend todo $d
9579 set queued($d) 1
9580 if {![info exists hastaggeddescendent($d)]} {
9581 incr nc
9586 set t2 [clock clicks -milliseconds]
9587 set loopix $i
9588 set tags {}
9589 foreach id [array names tagloc] {
9590 if {![info exists hastaggeddescendent($id)]} {
9591 foreach t $tagloc($id) {
9592 if {[lsearch -exact $tags $t] < 0} {
9593 lappend tags $t
9599 # remove tags that are ancestors of other tags
9600 for {set i 0} {$i < [llength $tags]} {incr i} {
9601 set a [lindex $tags $i]
9602 for {set j 0} {$j < $i} {incr j} {
9603 set b [lindex $tags $j]
9604 set r [anc_or_desc $a $b]
9605 if {$r == -1} {
9606 set tags [lreplace $tags $j $j]
9607 incr j -1
9608 incr i -1
9609 } elseif {$r == 1} {
9610 set tags [lreplace $tags $i $i]
9611 incr i -1
9612 break
9617 if {[array names growing] ne {}} {
9618 # graph isn't finished, need to check if any tag could get
9619 # eclipsed by another tag coming later. Simply ignore any
9620 # tags that could later get eclipsed.
9621 set ctags {}
9622 foreach t $tags {
9623 if {[is_certain $origid $t]} {
9624 lappend ctags $t
9627 if {$tags eq $ctags} {
9628 set cached_atags($origid) $tags
9629 } else {
9630 set tags $ctags
9632 } else {
9633 set cached_atags($origid) $tags
9635 set t3 [clock clicks -milliseconds]
9636 if {0 && $t3 - $t1 >= 100} {
9637 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
9638 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9640 return $tags
9643 # Return the list of IDs that have heads that are descendents of id,
9644 # including id itself if it has a head.
9645 proc descheads {id} {
9646 global arcnos arcstart arcids archeads idheads cached_dheads
9647 global allparents
9649 if {![info exists allparents($id)]} {
9650 return {}
9652 set aret {}
9653 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9654 # part-way along an arc; check it first
9655 set a [lindex $arcnos($id) 0]
9656 if {$archeads($a) ne {}} {
9657 validate_archeads $a
9658 set i [lsearch -exact $arcids($a) $id]
9659 foreach t $archeads($a) {
9660 set j [lsearch -exact $arcids($a) $t]
9661 if {$j > $i} break
9662 lappend aret $t
9665 set id $arcstart($a)
9667 set origid $id
9668 set todo [list $id]
9669 set seen($id) 1
9670 set ret {}
9671 for {set i 0} {$i < [llength $todo]} {incr i} {
9672 set id [lindex $todo $i]
9673 if {[info exists cached_dheads($id)]} {
9674 set ret [concat $ret $cached_dheads($id)]
9675 } else {
9676 if {[info exists idheads($id)]} {
9677 lappend ret $id
9679 foreach a $arcnos($id) {
9680 if {$archeads($a) ne {}} {
9681 validate_archeads $a
9682 if {$archeads($a) ne {}} {
9683 set ret [concat $ret $archeads($a)]
9686 set d $arcstart($a)
9687 if {![info exists seen($d)]} {
9688 lappend todo $d
9689 set seen($d) 1
9694 set ret [lsort -unique $ret]
9695 set cached_dheads($origid) $ret
9696 return [concat $ret $aret]
9699 proc addedtag {id} {
9700 global arcnos arcout cached_dtags cached_atags
9702 if {![info exists arcnos($id)]} return
9703 if {![info exists arcout($id)]} {
9704 recalcarc [lindex $arcnos($id) 0]
9706 catch {unset cached_dtags}
9707 catch {unset cached_atags}
9710 proc addedhead {hid head} {
9711 global arcnos arcout cached_dheads
9713 if {![info exists arcnos($hid)]} return
9714 if {![info exists arcout($hid)]} {
9715 recalcarc [lindex $arcnos($hid) 0]
9717 catch {unset cached_dheads}
9720 proc removedhead {hid head} {
9721 global cached_dheads
9723 catch {unset cached_dheads}
9726 proc movedhead {hid head} {
9727 global arcnos arcout cached_dheads
9729 if {![info exists arcnos($hid)]} return
9730 if {![info exists arcout($hid)]} {
9731 recalcarc [lindex $arcnos($hid) 0]
9733 catch {unset cached_dheads}
9736 proc changedrefs {} {
9737 global cached_dheads cached_dtags cached_atags
9738 global arctags archeads arcnos arcout idheads idtags
9740 foreach id [concat [array names idheads] [array names idtags]] {
9741 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9742 set a [lindex $arcnos($id) 0]
9743 if {![info exists donearc($a)]} {
9744 recalcarc $a
9745 set donearc($a) 1
9749 catch {unset cached_dtags}
9750 catch {unset cached_atags}
9751 catch {unset cached_dheads}
9754 proc rereadrefs {} {
9755 global idtags idheads idotherrefs mainheadid
9757 set refids [concat [array names idtags] \
9758 [array names idheads] [array names idotherrefs]]
9759 foreach id $refids {
9760 if {![info exists ref($id)]} {
9761 set ref($id) [listrefs $id]
9764 set oldmainhead $mainheadid
9765 readrefs
9766 changedrefs
9767 set refids [lsort -unique [concat $refids [array names idtags] \
9768 [array names idheads] [array names idotherrefs]]]
9769 foreach id $refids {
9770 set v [listrefs $id]
9771 if {![info exists ref($id)] || $ref($id) != $v} {
9772 redrawtags $id
9775 if {$oldmainhead ne $mainheadid} {
9776 redrawtags $oldmainhead
9777 redrawtags $mainheadid
9779 run refill_reflist
9782 proc listrefs {id} {
9783 global idtags idheads idotherrefs
9785 set x {}
9786 if {[info exists idtags($id)]} {
9787 set x $idtags($id)
9789 set y {}
9790 if {[info exists idheads($id)]} {
9791 set y $idheads($id)
9793 set z {}
9794 if {[info exists idotherrefs($id)]} {
9795 set z $idotherrefs($id)
9797 return [list $x $y $z]
9800 proc showtag {tag isnew} {
9801 global ctext tagcontents tagids linknum tagobjid
9803 if {$isnew} {
9804 addtohistory [list showtag $tag 0]
9806 $ctext conf -state normal
9807 clear_ctext
9808 settabs 0
9809 set linknum 0
9810 if {![info exists tagcontents($tag)]} {
9811 catch {
9812 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9815 if {[info exists tagcontents($tag)]} {
9816 set text $tagcontents($tag)
9817 } else {
9818 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9820 appendwithlinks $text {}
9821 $ctext conf -state disabled
9822 init_flist {}
9825 proc doquit {} {
9826 global stopped
9827 global gitktmpdir
9829 set stopped 100
9830 savestuff .
9831 destroy .
9833 if {[info exists gitktmpdir]} {
9834 catch {file delete -force $gitktmpdir}
9838 proc mkfontdisp {font top which} {
9839 global fontattr fontpref $font
9841 set fontpref($font) [set $font]
9842 button $top.${font}but -text $which -font optionfont \
9843 -command [list choosefont $font $which]
9844 label $top.$font -relief flat -font $font \
9845 -text $fontattr($font,family) -justify left
9846 grid x $top.${font}but $top.$font -sticky w
9849 proc choosefont {font which} {
9850 global fontparam fontlist fonttop fontattr
9851 global prefstop
9853 set fontparam(which) $which
9854 set fontparam(font) $font
9855 set fontparam(family) [font actual $font -family]
9856 set fontparam(size) $fontattr($font,size)
9857 set fontparam(weight) $fontattr($font,weight)
9858 set fontparam(slant) $fontattr($font,slant)
9859 set top .gitkfont
9860 set fonttop $top
9861 if {![winfo exists $top]} {
9862 font create sample
9863 eval font config sample [font actual $font]
9864 toplevel $top
9865 make_transient $top $prefstop
9866 wm title $top [mc "Gitk font chooser"]
9867 label $top.l -textvariable fontparam(which)
9868 pack $top.l -side top
9869 set fontlist [lsort [font families]]
9870 frame $top.f
9871 listbox $top.f.fam -listvariable fontlist \
9872 -yscrollcommand [list $top.f.sb set]
9873 bind $top.f.fam <<ListboxSelect>> selfontfam
9874 scrollbar $top.f.sb -command [list $top.f.fam yview]
9875 pack $top.f.sb -side right -fill y
9876 pack $top.f.fam -side left -fill both -expand 1
9877 pack $top.f -side top -fill both -expand 1
9878 frame $top.g
9879 spinbox $top.g.size -from 4 -to 40 -width 4 \
9880 -textvariable fontparam(size) \
9881 -validatecommand {string is integer -strict %s}
9882 checkbutton $top.g.bold -padx 5 \
9883 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9884 -variable fontparam(weight) -onvalue bold -offvalue normal
9885 checkbutton $top.g.ital -padx 5 \
9886 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9887 -variable fontparam(slant) -onvalue italic -offvalue roman
9888 pack $top.g.size $top.g.bold $top.g.ital -side left
9889 pack $top.g -side top
9890 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9891 -background white
9892 $top.c create text 100 25 -anchor center -text $which -font sample \
9893 -fill black -tags text
9894 bind $top.c <Configure> [list centertext $top.c]
9895 pack $top.c -side top -fill x
9896 frame $top.buts
9897 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9898 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9899 bind $top <Key-Return> fontok
9900 bind $top <Key-Escape> fontcan
9901 grid $top.buts.ok $top.buts.can
9902 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9903 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9904 pack $top.buts -side bottom -fill x
9905 trace add variable fontparam write chg_fontparam
9906 } else {
9907 raise $top
9908 $top.c itemconf text -text $which
9910 set i [lsearch -exact $fontlist $fontparam(family)]
9911 if {$i >= 0} {
9912 $top.f.fam selection set $i
9913 $top.f.fam see $i
9917 proc centertext {w} {
9918 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9921 proc fontok {} {
9922 global fontparam fontpref prefstop
9924 set f $fontparam(font)
9925 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9926 if {$fontparam(weight) eq "bold"} {
9927 lappend fontpref($f) "bold"
9929 if {$fontparam(slant) eq "italic"} {
9930 lappend fontpref($f) "italic"
9932 set w $prefstop.$f
9933 $w conf -text $fontparam(family) -font $fontpref($f)
9935 fontcan
9938 proc fontcan {} {
9939 global fonttop fontparam
9941 if {[info exists fonttop]} {
9942 catch {destroy $fonttop}
9943 catch {font delete sample}
9944 unset fonttop
9945 unset fontparam
9949 proc selfontfam {} {
9950 global fonttop fontparam
9952 set i [$fonttop.f.fam curselection]
9953 if {$i ne {}} {
9954 set fontparam(family) [$fonttop.f.fam get $i]
9958 proc chg_fontparam {v sub op} {
9959 global fontparam
9961 font config sample -$sub $fontparam($sub)
9964 proc doprefs {} {
9965 global maxwidth maxgraphpct
9966 global oldprefs prefstop showneartags showlocalchanges
9967 global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
9968 global tabstop limitdiffs autoselect extdifftool perfile_attrs
9970 set top .gitkprefs
9971 set prefstop $top
9972 if {[winfo exists $top]} {
9973 raise $top
9974 return
9976 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9977 limitdiffs tabstop perfile_attrs} {
9978 set oldprefs($v) [set $v]
9980 toplevel $top
9981 wm title $top [mc "Gitk preferences"]
9982 make_transient $top .
9983 label $top.ldisp -text [mc "Commit list display options"]
9984 grid $top.ldisp - -sticky w -pady 10
9985 label $top.spacer -text " "
9986 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9987 -font optionfont
9988 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9989 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9990 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9991 -font optionfont
9992 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9993 grid x $top.maxpctl $top.maxpct -sticky w
9994 frame $top.showlocal
9995 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9996 checkbutton $top.showlocal.b -variable showlocalchanges
9997 pack $top.showlocal.b $top.showlocal.l -side left
9998 grid x $top.showlocal -sticky w
9999 frame $top.autoselect
10000 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
10001 checkbutton $top.autoselect.b -variable autoselect
10002 pack $top.autoselect.b $top.autoselect.l -side left
10003 grid x $top.autoselect -sticky w
10005 label $top.ddisp -text [mc "Diff display options"]
10006 grid $top.ddisp - -sticky w -pady 10
10007 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
10008 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
10009 grid x $top.tabstopl $top.tabstop -sticky w
10010 frame $top.ntag
10011 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
10012 checkbutton $top.ntag.b -variable showneartags
10013 pack $top.ntag.b $top.ntag.l -side left
10014 grid x $top.ntag -sticky w
10015 frame $top.ldiff
10016 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
10017 checkbutton $top.ldiff.b -variable limitdiffs
10018 pack $top.ldiff.b $top.ldiff.l -side left
10019 grid x $top.ldiff -sticky w
10020 frame $top.lattr
10021 label $top.lattr.l -text [mc "Support per-file encodings"] -font optionfont
10022 checkbutton $top.lattr.b -variable perfile_attrs
10023 pack $top.lattr.b $top.lattr.l -side left
10024 grid x $top.lattr -sticky w
10026 entry $top.extdifft -textvariable extdifftool
10027 frame $top.extdifff
10028 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
10029 -padx 10
10030 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
10031 -command choose_extdiff
10032 pack $top.extdifff.l $top.extdifff.b -side left
10033 grid x $top.extdifff $top.extdifft -sticky w
10035 label $top.cdisp -text [mc "Colors: press to choose"]
10036 grid $top.cdisp - -sticky w -pady 10
10037 label $top.bg -padx 40 -relief sunk -background $bgcolor
10038 button $top.bgbut -text [mc "Background"] -font optionfont \
10039 -command [list choosecolor bgcolor {} $top.bg background setbg]
10040 grid x $top.bgbut $top.bg -sticky w
10041 label $top.fg -padx 40 -relief sunk -background $fgcolor
10042 button $top.fgbut -text [mc "Foreground"] -font optionfont \
10043 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
10044 grid x $top.fgbut $top.fg -sticky w
10045 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
10046 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
10047 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
10048 [list $ctext tag conf d0 -foreground]]
10049 grid x $top.diffoldbut $top.diffold -sticky w
10050 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
10051 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
10052 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
10053 [list $ctext tag conf dresult -foreground]]
10054 grid x $top.diffnewbut $top.diffnew -sticky w
10055 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
10056 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
10057 -command [list choosecolor diffcolors 2 $top.hunksep \
10058 "diff hunk header" \
10059 [list $ctext tag conf hunksep -foreground]]
10060 grid x $top.hunksepbut $top.hunksep -sticky w
10061 label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
10062 button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \
10063 -command [list choosecolor markbgcolor {} $top.markbgsep \
10064 [mc "marked line background"] \
10065 [list $ctext tag conf omark -background]]
10066 grid x $top.markbgbut $top.markbgsep -sticky w
10067 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
10068 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
10069 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
10070 grid x $top.selbgbut $top.selbgsep -sticky w
10072 label $top.cfont -text [mc "Fonts: press to choose"]
10073 grid $top.cfont - -sticky w -pady 10
10074 mkfontdisp mainfont $top [mc "Main font"]
10075 mkfontdisp textfont $top [mc "Diff display font"]
10076 mkfontdisp uifont $top [mc "User interface font"]
10078 frame $top.buts
10079 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
10080 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
10081 bind $top <Key-Return> prefsok
10082 bind $top <Key-Escape> prefscan
10083 grid $top.buts.ok $top.buts.can
10084 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10085 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10086 grid $top.buts - - -pady 10 -sticky ew
10087 bind $top <Visibility> "focus $top.buts.ok"
10090 proc choose_extdiff {} {
10091 global extdifftool
10093 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
10094 if {$prog ne {}} {
10095 set extdifftool $prog
10099 proc choosecolor {v vi w x cmd} {
10100 global $v
10102 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
10103 -title [mc "Gitk: choose color for %s" $x]]
10104 if {$c eq {}} return
10105 $w conf -background $c
10106 lset $v $vi $c
10107 eval $cmd $c
10110 proc setselbg {c} {
10111 global bglist cflist
10112 foreach w $bglist {
10113 $w configure -selectbackground $c
10115 $cflist tag configure highlight \
10116 -background [$cflist cget -selectbackground]
10117 allcanvs itemconf secsel -fill $c
10120 proc setbg {c} {
10121 global bglist
10123 foreach w $bglist {
10124 $w conf -background $c
10128 proc setfg {c} {
10129 global fglist canv
10131 foreach w $fglist {
10132 $w conf -foreground $c
10134 allcanvs itemconf text -fill $c
10135 $canv itemconf circle -outline $c
10138 proc prefscan {} {
10139 global oldprefs prefstop
10141 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10142 limitdiffs tabstop perfile_attrs} {
10143 global $v
10144 set $v $oldprefs($v)
10146 catch {destroy $prefstop}
10147 unset prefstop
10148 fontcan
10151 proc prefsok {} {
10152 global maxwidth maxgraphpct
10153 global oldprefs prefstop showneartags showlocalchanges
10154 global fontpref mainfont textfont uifont
10155 global limitdiffs treediffs perfile_attrs
10157 catch {destroy $prefstop}
10158 unset prefstop
10159 fontcan
10160 set fontchanged 0
10161 if {$mainfont ne $fontpref(mainfont)} {
10162 set mainfont $fontpref(mainfont)
10163 parsefont mainfont $mainfont
10164 eval font configure mainfont [fontflags mainfont]
10165 eval font configure mainfontbold [fontflags mainfont 1]
10166 setcoords
10167 set fontchanged 1
10169 if {$textfont ne $fontpref(textfont)} {
10170 set textfont $fontpref(textfont)
10171 parsefont textfont $textfont
10172 eval font configure textfont [fontflags textfont]
10173 eval font configure textfontbold [fontflags textfont 1]
10175 if {$uifont ne $fontpref(uifont)} {
10176 set uifont $fontpref(uifont)
10177 parsefont uifont $uifont
10178 eval font configure uifont [fontflags uifont]
10180 settabs
10181 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
10182 if {$showlocalchanges} {
10183 doshowlocalchanges
10184 } else {
10185 dohidelocalchanges
10188 if {$limitdiffs != $oldprefs(limitdiffs) ||
10189 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
10190 # treediffs elements are limited by path;
10191 # won't have encodings cached if perfile_attrs was just turned on
10192 catch {unset treediffs}
10194 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
10195 || $maxgraphpct != $oldprefs(maxgraphpct)} {
10196 redisplay
10197 } elseif {$showneartags != $oldprefs(showneartags) ||
10198 $limitdiffs != $oldprefs(limitdiffs)} {
10199 reselectline
10203 proc formatdate {d} {
10204 global datetimeformat
10205 if {$d ne {}} {
10206 set d [clock format $d -format $datetimeformat]
10208 return $d
10211 # This list of encoding names and aliases is distilled from
10212 # http://www.iana.org/assignments/character-sets.
10213 # Not all of them are supported by Tcl.
10214 set encoding_aliases {
10215 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
10216 ISO646-US US-ASCII us IBM367 cp367 csASCII }
10217 { ISO-10646-UTF-1 csISO10646UTF1 }
10218 { ISO_646.basic:1983 ref csISO646basic1983 }
10219 { INVARIANT csINVARIANT }
10220 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
10221 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
10222 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
10223 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
10224 { NATS-DANO iso-ir-9-1 csNATSDANO }
10225 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
10226 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
10227 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
10228 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
10229 { ISO-2022-KR csISO2022KR }
10230 { EUC-KR csEUCKR }
10231 { ISO-2022-JP csISO2022JP }
10232 { ISO-2022-JP-2 csISO2022JP2 }
10233 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
10234 csISO13JISC6220jp }
10235 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
10236 { IT iso-ir-15 ISO646-IT csISO15Italian }
10237 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
10238 { ES iso-ir-17 ISO646-ES csISO17Spanish }
10239 { greek7-old iso-ir-18 csISO18Greek7Old }
10240 { latin-greek iso-ir-19 csISO19LatinGreek }
10241 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
10242 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
10243 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
10244 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
10245 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
10246 { BS_viewdata iso-ir-47 csISO47BSViewdata }
10247 { INIS iso-ir-49 csISO49INIS }
10248 { INIS-8 iso-ir-50 csISO50INIS8 }
10249 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
10250 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
10251 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
10252 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
10253 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
10254 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
10255 csISO60Norwegian1 }
10256 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
10257 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
10258 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
10259 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
10260 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
10261 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
10262 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
10263 { greek7 iso-ir-88 csISO88Greek7 }
10264 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
10265 { iso-ir-90 csISO90 }
10266 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
10267 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
10268 csISO92JISC62991984b }
10269 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
10270 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
10271 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
10272 csISO95JIS62291984handadd }
10273 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
10274 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
10275 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
10276 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
10277 CP819 csISOLatin1 }
10278 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
10279 { T.61-7bit iso-ir-102 csISO102T617bit }
10280 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
10281 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
10282 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
10283 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
10284 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
10285 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
10286 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
10287 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
10288 arabic csISOLatinArabic }
10289 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
10290 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
10291 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
10292 greek greek8 csISOLatinGreek }
10293 { T.101-G2 iso-ir-128 csISO128T101G2 }
10294 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
10295 csISOLatinHebrew }
10296 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
10297 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
10298 { CSN_369103 iso-ir-139 csISO139CSN369103 }
10299 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
10300 { ISO_6937-2-add iso-ir-142 csISOTextComm }
10301 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
10302 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
10303 csISOLatinCyrillic }
10304 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
10305 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
10306 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
10307 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
10308 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
10309 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
10310 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
10311 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
10312 { ISO_10367-box iso-ir-155 csISO10367Box }
10313 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
10314 { latin-lap lap iso-ir-158 csISO158Lap }
10315 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
10316 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
10317 { us-dk csUSDK }
10318 { dk-us csDKUS }
10319 { JIS_X0201 X0201 csHalfWidthKatakana }
10320 { KSC5636 ISO646-KR csKSC5636 }
10321 { ISO-10646-UCS-2 csUnicode }
10322 { ISO-10646-UCS-4 csUCS4 }
10323 { DEC-MCS dec csDECMCS }
10324 { hp-roman8 roman8 r8 csHPRoman8 }
10325 { macintosh mac csMacintosh }
10326 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
10327 csIBM037 }
10328 { IBM038 EBCDIC-INT cp038 csIBM038 }
10329 { IBM273 CP273 csIBM273 }
10330 { IBM274 EBCDIC-BE CP274 csIBM274 }
10331 { IBM275 EBCDIC-BR cp275 csIBM275 }
10332 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
10333 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
10334 { IBM280 CP280 ebcdic-cp-it csIBM280 }
10335 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
10336 { IBM284 CP284 ebcdic-cp-es csIBM284 }
10337 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
10338 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
10339 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
10340 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
10341 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
10342 { IBM424 cp424 ebcdic-cp-he csIBM424 }
10343 { IBM437 cp437 437 csPC8CodePage437 }
10344 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
10345 { IBM775 cp775 csPC775Baltic }
10346 { IBM850 cp850 850 csPC850Multilingual }
10347 { IBM851 cp851 851 csIBM851 }
10348 { IBM852 cp852 852 csPCp852 }
10349 { IBM855 cp855 855 csIBM855 }
10350 { IBM857 cp857 857 csIBM857 }
10351 { IBM860 cp860 860 csIBM860 }
10352 { IBM861 cp861 861 cp-is csIBM861 }
10353 { IBM862 cp862 862 csPC862LatinHebrew }
10354 { IBM863 cp863 863 csIBM863 }
10355 { IBM864 cp864 csIBM864 }
10356 { IBM865 cp865 865 csIBM865 }
10357 { IBM866 cp866 866 csIBM866 }
10358 { IBM868 CP868 cp-ar csIBM868 }
10359 { IBM869 cp869 869 cp-gr csIBM869 }
10360 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
10361 { IBM871 CP871 ebcdic-cp-is csIBM871 }
10362 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
10363 { IBM891 cp891 csIBM891 }
10364 { IBM903 cp903 csIBM903 }
10365 { IBM904 cp904 904 csIBBM904 }
10366 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
10367 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
10368 { IBM1026 CP1026 csIBM1026 }
10369 { EBCDIC-AT-DE csIBMEBCDICATDE }
10370 { EBCDIC-AT-DE-A csEBCDICATDEA }
10371 { EBCDIC-CA-FR csEBCDICCAFR }
10372 { EBCDIC-DK-NO csEBCDICDKNO }
10373 { EBCDIC-DK-NO-A csEBCDICDKNOA }
10374 { EBCDIC-FI-SE csEBCDICFISE }
10375 { EBCDIC-FI-SE-A csEBCDICFISEA }
10376 { EBCDIC-FR csEBCDICFR }
10377 { EBCDIC-IT csEBCDICIT }
10378 { EBCDIC-PT csEBCDICPT }
10379 { EBCDIC-ES csEBCDICES }
10380 { EBCDIC-ES-A csEBCDICESA }
10381 { EBCDIC-ES-S csEBCDICESS }
10382 { EBCDIC-UK csEBCDICUK }
10383 { EBCDIC-US csEBCDICUS }
10384 { UNKNOWN-8BIT csUnknown8BiT }
10385 { MNEMONIC csMnemonic }
10386 { MNEM csMnem }
10387 { VISCII csVISCII }
10388 { VIQR csVIQR }
10389 { KOI8-R csKOI8R }
10390 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
10391 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
10392 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
10393 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
10394 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
10395 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
10396 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
10397 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
10398 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
10399 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
10400 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
10401 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
10402 { IBM1047 IBM-1047 }
10403 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
10404 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
10405 { UNICODE-1-1 csUnicode11 }
10406 { CESU-8 csCESU-8 }
10407 { BOCU-1 csBOCU-1 }
10408 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
10409 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
10410 l8 }
10411 { ISO-8859-15 ISO_8859-15 Latin-9 }
10412 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
10413 { GBK CP936 MS936 windows-936 }
10414 { JIS_Encoding csJISEncoding }
10415 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
10416 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
10417 EUC-JP }
10418 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
10419 { ISO-10646-UCS-Basic csUnicodeASCII }
10420 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
10421 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
10422 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
10423 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
10424 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
10425 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
10426 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
10427 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
10428 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
10429 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
10430 { Adobe-Standard-Encoding csAdobeStandardEncoding }
10431 { Ventura-US csVenturaUS }
10432 { Ventura-International csVenturaInternational }
10433 { PC8-Danish-Norwegian csPC8DanishNorwegian }
10434 { PC8-Turkish csPC8Turkish }
10435 { IBM-Symbols csIBMSymbols }
10436 { IBM-Thai csIBMThai }
10437 { HP-Legal csHPLegal }
10438 { HP-Pi-font csHPPiFont }
10439 { HP-Math8 csHPMath8 }
10440 { Adobe-Symbol-Encoding csHPPSMath }
10441 { HP-DeskTop csHPDesktop }
10442 { Ventura-Math csVenturaMath }
10443 { Microsoft-Publishing csMicrosoftPublishing }
10444 { Windows-31J csWindows31J }
10445 { GB2312 csGB2312 }
10446 { Big5 csBig5 }
10449 proc tcl_encoding {enc} {
10450 global encoding_aliases tcl_encoding_cache
10451 if {[info exists tcl_encoding_cache($enc)]} {
10452 return $tcl_encoding_cache($enc)
10454 set names [encoding names]
10455 set lcnames [string tolower $names]
10456 set enc [string tolower $enc]
10457 set i [lsearch -exact $lcnames $enc]
10458 if {$i < 0} {
10459 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
10460 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
10461 set i [lsearch -exact $lcnames $encx]
10464 if {$i < 0} {
10465 foreach l $encoding_aliases {
10466 set ll [string tolower $l]
10467 if {[lsearch -exact $ll $enc] < 0} continue
10468 # look through the aliases for one that tcl knows about
10469 foreach e $ll {
10470 set i [lsearch -exact $lcnames $e]
10471 if {$i < 0} {
10472 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
10473 set i [lsearch -exact $lcnames $ex]
10476 if {$i >= 0} break
10478 break
10481 set tclenc {}
10482 if {$i >= 0} {
10483 set tclenc [lindex $names $i]
10485 set tcl_encoding_cache($enc) $tclenc
10486 return $tclenc
10489 proc gitattr {path attr default} {
10490 global path_attr_cache
10491 if {[info exists path_attr_cache($attr,$path)]} {
10492 set r $path_attr_cache($attr,$path)
10493 } else {
10494 set r "unspecified"
10495 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
10496 regexp "(.*): encoding: (.*)" $line m f r
10498 set path_attr_cache($attr,$path) $r
10500 if {$r eq "unspecified"} {
10501 return $default
10503 return $r
10506 proc cache_gitattr {attr pathlist} {
10507 global path_attr_cache
10508 set newlist {}
10509 foreach path $pathlist {
10510 if {![info exists path_attr_cache($attr,$path)]} {
10511 lappend newlist $path
10514 set lim 1000
10515 if {[tk windowingsystem] == "win32"} {
10516 # windows has a 32k limit on the arguments to a command...
10517 set lim 30
10519 while {$newlist ne {}} {
10520 set head [lrange $newlist 0 [expr {$lim - 1}]]
10521 set newlist [lrange $newlist $lim end]
10522 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
10523 foreach row [split $rlist "\n"] {
10524 if {[regexp "(.*): encoding: (.*)" $row m path value]} {
10525 if {[string index $path 0] eq "\""} {
10526 set path [encoding convertfrom [lindex $path 0]]
10528 set path_attr_cache($attr,$path) $value
10535 proc get_path_encoding {path} {
10536 global gui_encoding perfile_attrs
10537 set tcl_enc $gui_encoding
10538 if {$path ne {} && $perfile_attrs} {
10539 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
10540 if {$enc2 ne {}} {
10541 set tcl_enc $enc2
10544 return $tcl_enc
10547 # First check that Tcl/Tk is recent enough
10548 if {[catch {package require Tk 8.4} err]} {
10549 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
10550 Gitk requires at least Tcl/Tk 8.4."]
10551 exit 1
10554 # defaults...
10555 set wrcomcmd "git diff-tree --stdin -p --pretty"
10557 set gitencoding {}
10558 catch {
10559 set gitencoding [exec git config --get i18n.commitencoding]
10561 if {$gitencoding == ""} {
10562 set gitencoding "utf-8"
10564 set tclencoding [tcl_encoding $gitencoding]
10565 if {$tclencoding == {}} {
10566 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
10569 set gui_encoding [encoding system]
10570 catch {
10571 set enc [exec git config --get gui.encoding]
10572 if {$enc ne {}} {
10573 set tclenc [tcl_encoding $enc]
10574 if {$tclenc ne {}} {
10575 set gui_encoding $tclenc
10576 } else {
10577 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
10582 set mainfont {Helvetica 9}
10583 set textfont {Courier 9}
10584 set uifont {Helvetica 9 bold}
10585 set tabstop 8
10586 set findmergefiles 0
10587 set maxgraphpct 50
10588 set maxwidth 16
10589 set revlistorder 0
10590 set fastdate 0
10591 set uparrowlen 5
10592 set downarrowlen 5
10593 set mingaplen 100
10594 set cmitmode "patch"
10595 set wrapcomment "none"
10596 set showneartags 1
10597 set maxrefs 20
10598 set maxlinelen 200
10599 set showlocalchanges 1
10600 set limitdiffs 1
10601 set datetimeformat "%Y-%m-%d %H:%M:%S"
10602 set autoselect 1
10603 set perfile_attrs 0
10605 set extdifftool "meld"
10607 set colors {green red blue magenta darkgrey brown orange}
10608 set bgcolor white
10609 set fgcolor black
10610 set diffcolors {red "#00a000" blue}
10611 set diffcontext 3
10612 set ignorespace 0
10613 set selectbgcolor gray85
10614 set markbgcolor "#e0e0ff"
10616 set circlecolors {white blue gray blue blue}
10618 # button for popping up context menus
10619 if {[tk windowingsystem] eq "aqua"} {
10620 set ctxbut <Button-2>
10621 } else {
10622 set ctxbut <Button-3>
10625 ## For msgcat loading, first locate the installation location.
10626 if { [info exists ::env(GITK_MSGSDIR)] } {
10627 ## Msgsdir was manually set in the environment.
10628 set gitk_msgsdir $::env(GITK_MSGSDIR)
10629 } else {
10630 ## Let's guess the prefix from argv0.
10631 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
10632 set gitk_libdir [file join $gitk_prefix share gitk lib]
10633 set gitk_msgsdir [file join $gitk_libdir msgs]
10634 unset gitk_prefix
10637 ## Internationalization (i18n) through msgcat and gettext. See
10638 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
10639 package require msgcat
10640 namespace import ::msgcat::mc
10641 ## And eventually load the actual message catalog
10642 ::msgcat::mcload $gitk_msgsdir
10644 catch {source ~/.gitk}
10646 font create optionfont -family sans-serif -size -12
10648 parsefont mainfont $mainfont
10649 eval font create mainfont [fontflags mainfont]
10650 eval font create mainfontbold [fontflags mainfont 1]
10652 parsefont textfont $textfont
10653 eval font create textfont [fontflags textfont]
10654 eval font create textfontbold [fontflags textfont 1]
10656 parsefont uifont $uifont
10657 eval font create uifont [fontflags uifont]
10659 setoptions
10661 # check that we can find a .git directory somewhere...
10662 if {[catch {set gitdir [gitdir]}]} {
10663 show_error {} . [mc "Cannot find a git repository here."]
10664 exit 1
10666 if {![file isdirectory $gitdir]} {
10667 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
10668 exit 1
10671 set selecthead {}
10672 set selectheadid {}
10674 set revtreeargs {}
10675 set cmdline_files {}
10676 set i 0
10677 set revtreeargscmd {}
10678 foreach arg $argv {
10679 switch -glob -- $arg {
10680 "" { }
10681 "--" {
10682 set cmdline_files [lrange $argv [expr {$i + 1}] end]
10683 break
10685 "--select-commit=*" {
10686 set selecthead [string range $arg 16 end]
10688 "--argscmd=*" {
10689 set revtreeargscmd [string range $arg 10 end]
10691 default {
10692 lappend revtreeargs $arg
10695 incr i
10698 if {$selecthead eq "HEAD"} {
10699 set selecthead {}
10702 if {$i >= [llength $argv] && $revtreeargs ne {}} {
10703 # no -- on command line, but some arguments (other than --argscmd)
10704 if {[catch {
10705 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
10706 set cmdline_files [split $f "\n"]
10707 set n [llength $cmdline_files]
10708 set revtreeargs [lrange $revtreeargs 0 end-$n]
10709 # Unfortunately git rev-parse doesn't produce an error when
10710 # something is both a revision and a filename. To be consistent
10711 # with git log and git rev-list, check revtreeargs for filenames.
10712 foreach arg $revtreeargs {
10713 if {[file exists $arg]} {
10714 show_error {} . [mc "Ambiguous argument '%s': both revision\
10715 and filename" $arg]
10716 exit 1
10719 } err]} {
10720 # unfortunately we get both stdout and stderr in $err,
10721 # so look for "fatal:".
10722 set i [string first "fatal:" $err]
10723 if {$i > 0} {
10724 set err [string range $err [expr {$i + 6}] end]
10726 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
10727 exit 1
10731 set nullid "0000000000000000000000000000000000000000"
10732 set nullid2 "0000000000000000000000000000000000000001"
10733 set nullfile "/dev/null"
10735 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
10737 set runq {}
10738 set history {}
10739 set historyindex 0
10740 set fh_serial 0
10741 set nhl_names {}
10742 set highlight_paths {}
10743 set findpattern {}
10744 set searchdirn -forwards
10745 set boldrows {}
10746 set boldnamerows {}
10747 set diffelide {0 0}
10748 set markingmatches 0
10749 set linkentercount 0
10750 set need_redisplay 0
10751 set nrows_drawn 0
10752 set firsttabstop 0
10754 set nextviewnum 1
10755 set curview 0
10756 set selectedview 0
10757 set selectedhlview [mc "None"]
10758 set highlight_related [mc "None"]
10759 set highlight_files {}
10760 set viewfiles(0) {}
10761 set viewperm(0) 0
10762 set viewargs(0) {}
10763 set viewargscmd(0) {}
10765 set selectedline {}
10766 set numcommits 0
10767 set loginstance 0
10768 set cmdlineok 0
10769 set stopped 0
10770 set stuffsaved 0
10771 set patchnum 0
10772 set lserial 0
10773 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
10774 setcoords
10775 makewindow
10776 # wait for the window to become visible
10777 tkwait visibility .
10778 wm title . "[file tail $argv0]: [file tail [pwd]]"
10779 readrefs
10781 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
10782 # create a view for the files/dirs specified on the command line
10783 set curview 1
10784 set selectedview 1
10785 set nextviewnum 2
10786 set viewname(1) [mc "Command line"]
10787 set viewfiles(1) $cmdline_files
10788 set viewargs(1) $revtreeargs
10789 set viewargscmd(1) $revtreeargscmd
10790 set viewperm(1) 0
10791 set vdatemode(1) 0
10792 addviewmenu 1
10793 .bar.view entryconf [mca "Edit view..."] -state normal
10794 .bar.view entryconf [mca "Delete view"] -state normal
10797 if {[info exists permviews]} {
10798 foreach v $permviews {
10799 set n $nextviewnum
10800 incr nextviewnum
10801 set viewname($n) [lindex $v 0]
10802 set viewfiles($n) [lindex $v 1]
10803 set viewargs($n) [lindex $v 2]
10804 set viewargscmd($n) [lindex $v 3]
10805 set viewperm($n) 1
10806 addviewmenu $n
10809 getcommits {}